程式語言學習心得 (精簡版) -- 不要害怕遺忘

2021-02-28 蔡超談軟體

引子

對於計算機從業者而言不論你的母語是什麼語言,中文,英語或是法語,西班牙語等,你的第一工作語言都是程式語言,你一定聽說過那句話 「talk is cheap show me the code"。所以,快速學習和掌握程式語言一直以來都是每一個工程師夢最想要擁有的超能力。

我從小學開始學習編程,在後來17年的職業生涯中也主動和被動的學習了一眾程式語言,如C/C++,Java,Python,Haskell,Groovy,Scala,Clojure,Go等等,在這期間付出了很多努力,取得了不少經驗,當然也走過了更多彎路。下面分享一下自己的學習心得,希望可以對大家的學習有所幫助和借鑑。

掌握編程範式優於牢記語法

各種程式語言裡的獨特語法簡直是五花八門,下面就隨便選取了其中幾種語言,看看你們知道他們都是什麼語言嗎?

1.

def biophony[T <: Animal](things: Seq[T]) = things map (_.sound)def biophony[T >: Animal](things: Seq[T]) = things map (_.sound())

2.

quicksort [] = []quicksort (x:xs) =                let smaller = [a|a<-xs,a<=x]                   larger = [a|a<-xs, a>x]               in quicksort smaller ++[x]++ quicksort larger

3.

my @sorted = sort {$a <=> $b} @input;my @results = map {$_ * 2 + 1} @input;

很多工程師往往把學習語言的重點放在了學習不同語言的語法上,而忽略了語言背後的思想及適合的應用場景。

其實對於程式語言的學習,意義最大,收穫最大的就是對於編程思想的學習。正如著名的計算機學者,首位圖靈獎獲得者,Alan Perlis說的那樣如果一個程式語言不能夠影響你的編程思維,這個語言便不值得學習。

「A language that doesn’t effect the way you think about programming, is not worth knowing.」 - Alan Perlis

程序語言的編程思想主要受到編程範式的影響,如果了解這點你就會發現很多新語言其實是新瓶裝老酒。

編程範式(programming paradigm):

官方的定義是:A programming paradigm is a style, or 「way,」 of programming. (一種編程風格和方式)

以下是目前廣泛應用的一些編程範式:

結構化 (Structured)

函數式 (Functional)

面向對象 (Object Oriented)

關於這些典型編程範式相信你已經有所耳聞,也可以在網絡上找到很多詳細的相關資料,這裡就不再贅述,僅通過一些簡單的實例對比,來讓大家認識到不同編程範式對程序設計思想的影響。

結構化  vs. 函數式

我們通過快速排序的實現來看看這兩種編程範式的差別:

快速排序 結構化實現:Groovy語言實現

class QuickSort {          private static void exch(int pos1,int pos2,List data){             int tmp=data[pos1];             data[pos1]=data[pos2];             data[pos2]=tmp;       }       private static void partition (int lo,int hi, List a){                                if (lo<0||hi<0||lo>=hi-1){                    return;             }             int midValue=a[lo];             int i=lo+1;int j=hi;             while (true){                    while(i<hi&&a[i]<midValue){                           i++;                    }                    while(j>lo&&a[j]>midValue){                           j--;                    }                    if (i>=j) break;                    exch(i,j,a);                     }             exch(i,lo,a);                    partition(lo,i-1,a);                          partition(i+1,hi,a);                    }       public static List sort(List a){             int lo=0; int hi=a.size()-1;             partition(lo, hi, a);                   return a;           }}

快速排序 函數式實現:Groovy實現

def quickSort_fp(List list){       if (list.size()==0){             return []       }       def x=list[0]        def smaller=list.findAll{it<x}       def mid=list.findAll{it==x}       def larger=list.findAll{it>x}           return quickSort_fp(smaller)+mid+quickSort_fp(larger)       }

通過以上比較你會發現:對於結構化編程,我們要通過程序來告訴機器怎麼做,而函數式編程則更像是通過程序告訴機器我們想要什麼。

函數式 vs 面向對象

這裡我們通過實現一個通用的計算過程計時功能來比較。大家可以細細體會其中的不同。

面向對象 Go語言實現

以下利用Decorator模式來為不同的Caculator實現的Caculate過程計時 (如果對於下面程序有些疑惑,建議先回顧一下設計模式裡的Decorator模式)

type Caculator interface {  Caculate(op int) int}
type TimerDecorator struct { innerCal Caculator}
func NewTimerFn(c Caculator) Caculator { return &TimerDecorator{ innerCal: c, }}
func (td *TimerDecorator) Caculate(op int) int { start := time.Now() ret := td.innerCal.Caculate(op) fmt.Println("time spent:", time.Since(start).Seconds()) return ret}
type SlowCal struct {}
func (sc *SlowCal) Caculate(op int) int { time.Sleep(time.Second * 1) return op}
func TestOO(t *testing.T) {   tf := NewTimerFn(&SlowCal{}) t.Log(tf.Caculate(10))}

函數式 Go語言實現

以下也是通過類似於Decorator的思路來為不同的方法實現添加運行過程計時

func timeSpent(inner func(op int) int) func(op int) int {  return func(n int) int {    start := time.Now()    ret := inner(n)    fmt.Println("time spent:",      time.Since(start).Seconds())    return ret  }}
func slowFun(op int) int { time.Sleep(time.Second * 1) return op}
func TestFn(t *testing.T) { tsSF := timeSpent(slowFun) t.Log(tsSF(10))}

通過上面的比較大家可以有個簡單認識,在函數式編程中函數是第一公民可以作為參數和返回值,而在面向對象編程中對象才是第一公民。

可以類比,但不要翻譯

在學習程式語言時,如果我們已經了掌握了一種語言,通過類比,尤其是比較不同點,可以有助於我們更快的掌握另一種新的語言。

要注意的是學習程式語言也和我們學習自然語言一樣,要掌握不同語言的特點,習慣用法,否則就會出現類似於中式英語這樣的問題。不管後來學習了什麼語言,都是先用熟悉的語言的方式實現,然後翻譯成另一種語言,我們常常可以看到C++語言描述的C程序,Go語言描述的Java程序等。

下面給大家舉個例子:

讓我們來生成一副撲克。

Java中的實現:

public static void main(String[] args) {     List<String> cards = new ArrayList<>();     for (int i=2;i<12;i++){       cards.add(String.valueOf(i));     }     cards.addAll(List.of("J","Q","K","A"));     System.out.println(cards);  }

Java程式設計師,學習了Python以後,通常會實現成這樣

cards = ['J','Q','K','A']for n in range(2,11):  cards.append(str(n))print cards

上面的Python代碼雖然也可以準確的實現功能,但在一個Python程式設計師眼中上面的代碼總覺得不是那麼地道,下面是地道的python程序

cards = [str(n) for n in range(2,11)] + list('JQKA')print cards

專注語言特性而不是語法糖

上面談到寫地道的程序,學好程式語言中的「語法糖」,通常可以讓我的代碼更簡化和「看上去」很地道,因此也有很多程式設計師非常熱衷於此,猶如對於IDE的快捷方式的熱衷,似乎這些是一個資深,高效程式設計師的標誌。

什麼是語法糖呢?

語言中某種特殊的語法

對語言的功能並沒有影響

對程式設計師有更好的易用性

增加程序的可讀性

一些語法糖的例子:

Python中union兩個list

Go交換兩個變量的值

上面由語法糖帶來的地道我之所以用了「看上去」這個詞來修飾,就是因為要想真正的寫出地道的程序,比掌握語法糖更重要的是掌握語言的特性。

什麼是語言的特性呢?

我們以Go和Java語言中並發機制特性為例:

1 基本並發單元

Go中採用獨特的協程(Goroutine)作為基本並發單元,這一定會讓Java程式設計師聯想起線程(Thread),並不免在編寫Go並發程序時引入很多編寫多線程程序的思維,實際由於兩者間存在著很多差異,直接以多線程的編程思想來編寫多協程的程序有時是不適合的。

先讓我們簡單看看兩者的差異:

雖然,我們沒有完全列出兩者的差異,但是你也可以發現像Java程序中常見的線程池,在Go程序中很多情況下並不會帶來像Java中那樣的性能提升。

2. 並發機制,

Java通常採用共享內存機制來進行並發控制,而Go中則支持了CSP(Communicating Sequential Processes)機制。

下面通過典型的生產者和消費者並發任務來比較兩種方式不同的特性:

共享內存方式(Java實現)

import java.util.LinkedList;import java.util.Queue;import org.junit.jupiter.api.Test;
class Producer implements Runnable {  private Queue<Integer> sharedData; public Producer(Queue<Integer> sharedData) { this.sharedData = sharedData;
}
@Override public void run() { for (int i = 0; i < 100; i++) { synchronized (this.sharedData) { try {
while (this.sharedData.size() != 0) { this.sharedData.wait(); } this.sharedData.add(i); System.out.printf("Put data %d \n", i); this.sharedData.notify(); } catch (InterruptedException e) { e.printStackTrace(); }
} }
}}
class Consumer implements Runnable {  private Queue<Integer> sharedData; public Consumer(Queue<Integer> sharedData) { this.sharedData = sharedData; }
@Override public void run() { while (true) { synchronized (this.sharedData) { try { while (this.sharedData.size() == 0) { this.sharedData.wait(); } System.out.println(this.sharedData.poll()); if (this.sharedData.size() == 0) { this.sharedData.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } }
    } }}
class ProducerConsumer { @Test void test() throws InterruptedException { Queue<Integer> sharedData = new LinkedList<>(); new Thread(new Producer(sharedData)).start(); new Thread(new Consumer(sharedData)).start(); Thread.sleep(2 * 1000);  }}

利用共享的Queue來實現生產者和消費者之間的數據傳遞,為了保證數據在多線程間同步,我們使用了鎖。

CSP方式 (Go實現)

package Demoimport (  "fmt"  "testing"  "time")
func Producer(ch chan int) { for i := 0; i < 100; i++ { fmt.Printf("put %d \n", i) ch <- i } close(ch)}
func Consumer(ch chan int) { for { select { case i, ok := <-ch: if !ok { fmt.Println("done.") return } fmt.Println(i)
} }}
func TestFn(t *testing.T) { ch := make(chan int) go Producer(ch) go Consumer(ch) time.Sleep(time.Second * 3)}

Go這是利用CSP機制中的channel在生產者和消費者之間傳遞數據。

好的代碼風格不能代替好的設計

代碼是軟體實現的最終形式。但是對於更為複雜的軟體而言,我們僅僅在代碼層面整潔,復用及高可讀性還是遠遠不夠。

人們總是通過更高層次的抽象來實現簡化。猶如,程式語言由機器語言,到彙編語言,再到高級語言不斷演進,抽象層次不斷提高。更高的抽象層次,能夠更加有助於人們去了理解和構建更複雜的軟體。

所以,在每個抽象層面,都要考慮簡潔,復用和易理解。而且軟體的設計過程及人們理解軟體設計的過程也通常是自頂向下的。這就產生了指導人們做好高層抽象的設計模式,甚至更高抽象層面的架構模式。

作為合格程式設計師光學習代碼的整潔之道是不夠的,還有學習更高層面的整潔和復用。

下面我們還是用一個例子來說明

這裡模擬我們要實現一個可以擴展使用不同支付方式的支付過程:

type PayChannel int
const ( AliPay PayChannel = 1 WechatPay PayChannel = 2)
func payWithAli(price float64) error { fmt.Printf("Pay with Alipay %f\n", price) return nil}
func payWithWechat(price float64) error { fmt.Printf("Pay with Wechat %f\n", price) return nil}
func PayWith(channel PayChannel, price float64) error { switch channel { case AliPay: return payWithAli(price) case WechatPay: return payWithWechat(price) default: return errors.New("not support the channel") }}

上面的代碼在編碼風格上是整潔的,可讀性也不錯。但我們會發現每增加一種支付模式我們都要修改PayWith這個方法,在switch中加入一個對應的分支。

通過利用面向對象中的命令模式的設計思想,我們可以將代碼優化為如下(當然這裡是採用函數式編程來實現這個命令模式的思想的)

type PayFunc func(price float64) error
func payWithAli(price float64) error { fmt.Printf("Pay with Alipay %f\n", price) return nil}
func payWithWechat(price float64) error { fmt.Printf("Pay with Wechat %f\n", price) return nil}
func Pay(payMethod PayFunc, price float64) error { return payMethod(price)}

現在可以看到,新增支付方式時,Pay方法完全不用做任何修改。

這裡給大家推薦兩本設計模式方面的經典書籍

不要害怕遺忘和混淆

「學了也用不上,很快就忘記了」常常會成為很多程式設計師拒絕學習新的程式語言的藉口。

遺忘和混淆都是正常的,人類記憶就是這樣。

這是遺忘曲線,在一開始的遺忘率是最高的。對於語言學習,我的經驗是我們會很快忘記我們學到的特殊語法,留存下來會是我們對語言編程範式,編程特性的理解。所以,正如前面我們已經提到過的《黑客與畫家》中觀點:學習了Lisp,即使你在工作中極少使用到它,你也會成為一個更優秀的程式設計師。

如果你是按照我們前面所說的方式充分掌握每種語言的特性並了解編程範式,你所遺忘和混淆的更多的是語法。通過寫上一,兩天,甚至幾小時的程序,你很快就會發現所有那些對於這種語言的技能就都回來了。這好比練過健身的人,一段時間不練,肌肉會有流失,但是與從來沒有練過的人不同,他們通過訓練,肌肉很快能夠恢復原有的狀態,就是所謂的肌肉記憶,我們的大腦記憶也是這樣的。

所以,不要因為害怕遺忘和混淆就不去學習新的語言,他們不僅可以拓寬你的編程思路,一旦需要你便可以經過較短時間從回巔峰!

不要讓遺忘成為你放棄學習的藉口,讓遺忘成為一種提煉。

「Stay hungry,stay foolish." -- Steve Jobs.

相關焦點

  • 程式語言學習心得 (完全版) -- 不要害怕遺忘和混淆
    我從小學開始學習編程,在後來17年的職業生涯中也主動和被動的學習了一眾程式語言,如C/C++,Java,Python,Haskell,Groovy,Scala,Clojure,Go等等,在這期間付出了很多努力,取得了不少經驗,當然也走過了更多彎路。下面分享一下自己的學習心得,希望可以對大家的學習有所幫助和借鑑。
  • 如何解決知識遺忘,以及學習新知識的方法
    ,也就是關於編程學習的一些方法!有的人會覺得,這些東西,寫來寫去就是那麼回事,尤其是有了編程經驗的程式設計師,更會覺得,編程的學習方法就那麼回事,花時間學唄,其實我不這麼認為!直到有一次我請教了幾位大佬前輩,得到以下兩句話:1、不要把知識遺忘當成問題去解決,你解決不了的,知識遺忘是一件正常的事情
  • 零基礎如何學習編程?
    今年的8月,剛好工作上想要開發一個基於微信的英語學習網站,藉此機會,我決定好好學習一下編程。在此之前,我所有關於程序的知識僅僅來源於兩部分,一是本科時期的C語言必修課,現在已經忘的一乾二淨,不過好歹我從中明白編程是怎麼回事;第二部分是大概兩年前由於工作關係學的一些Python,不過只是皮毛,現在忘的七七八八。
  • PPT,世界上最牛逼的程式語言?
    去年,新東方的年會火了,全都是因為一首改編的歌曲《沙漠駱駝》,隨之火的還有一種勝過任何開發語言的新秀
  • 一個資深C語言工程師說如何學習C語言
    今天本人就與大家一起談談如何學習C語言或者說學習C語言應從哪幾方面著手。了解一些基本知識一.C語言的背景就個人感觸,無論學習哪門語言首先應該了解一下自己所學語言的背景,也可以說它的發展史。C語言屬於高級程序語言的一種,它的前身是「ALGOL」。其創始人是布朗·W·卡尼漢和丹尼斯·M·利奇。
  • 兩年,我學會了所有的程式語言!
    本文想要探討的一個話題是:作為一個程式設計師,如何看待這麼多的程式語言?是不是要學習?該怎麼學習?其實很多人都有這樣的體會,當你學會了一門語言以後,再去學習新的語言,難度會大大降低,因為很多概念是一樣的, 只是換了一種包裝形式,但是每一門語言都有自己獨特之處,比如Ruby可以輕鬆地在運行時「打開」一個類,對這個類增加行為,還有method_missing的處理,這種特性,讓它特別適合去實現DSL,RoR的火爆也就不足為怪了。
  • 編程入門寶典,剛開始學習編程新手必看的5點建議!
    1、選擇程式語言  編程首要還是選擇好適合自己的語言。  程式語言有:C/C++、java、VB、python、actionscript3、javascript、html、css、js等。  有人覺得,C/C++是入門的首選語言,入門簡單。但是,如果你要精通它,怎麼說也需要大概十年的功夫。
  • 男孩子天生就比女孩子更適合學習編程嗎?
    在大眾的潛意識裡,學習編程似乎自帶性別壁壘,「男孩子天生就比女孩子更適合學習編程」「男孩子思維更敏捷學編程優勢更大」。難道男孩女孩間的思維差異,天生就有文理之分嗎?回答當然是:NO!可見,編程並不是男生獨佔的天下。如今,也有越來越多的女孩們,在計算機、編程和開發應用軟體上展現出她們的才華,以實際成績來改變人們的偏見。
  • 初學者的編程自學指南
    使用Google的基本搜索功能就能夠解決大部分問題了,當然,如果想要學習更加高級的技巧,這裡有一份指南:如何用好Google等搜尋引擎?。  英語  你不得不承認,目前為止,編程世界的主流語言還是英文,所以英語能力的好壞決定了你編程能力的上界。中文資料和英文資料相比,實在是太匱乏了,就質量而言,也相對較低。
  • 我是如何自學C語言的(一個菜鳥的學習路)
    utm_source=copy 首先,本人也是一個菜鳥,目前依然還在學習,當初在我開始自學C語言的時候,走過了好多彎路,如果你看到這篇文章,希望你不要走我走過的彎路,這也是我寫這篇文章的目的。我只是把自己覺得是坑,是彎路的地方,結合自己的看法,複述給你聽,希望你有自己的理解,能對你有一點幫助。
  • 從C、C++、Java到Python,編程入門到底學什麼語言好?
    回顧程式語言幾十年來的興衰起伏,似乎也折射了整個信息產業的變遷消亡,想要在技術的洪流裡激流勇進,找準並學精一兩門程式語言更加顯得至關重要。「有人不喜歡花括號,開發了Python;有人在一個周末設計了出了JavaScript;有人因為上班太無聊,於是發明了C語言」。關於程式語言的八卦軼事很多,但歸根結底,一個程式語言的誕生一定是需求的推動。
  • 如何學習好R語言?【全套R語音書籍+視頻下載】
    包括了閱讀經典的教材、代碼、論文、學習公開課。 - 通過牛人來學習。 包括同行的聚會、討論、大牛的博客、微博、twitter、RSS。 - 通過練習來學習。 包括代碼練習題、參加kaggle比賽、解決實際工作中的難題。 - 通過分享來學習。 包括自己寫筆記、寫博客、寫書、翻譯書,和同伴分享交流、培訓新人。# 全套R語音書籍下載。關注公眾號後,回覆:R語言
  • 老外總結的編程技巧
    學習一門新的程式語言(Learn a new programming language)學習一門新的程式語言將有助於你開拓新的思維方式,特別是當你使用不熟悉的語言時,你將學習到很多種思維方法應用到語言中。而所學習到的新思維方式,你可以運用在你所熟知的語言中。
  • 【學習心得】情緒管理心得體會
    通過學習網絡學院第四季度的必修課《情緒管理的快速實踐方法》,使我對情緒管理又有了全新的認識和透徹的理解。目前,各行各業的工作者都面臨著極大的壓力,適當的壓力可以促進我們更好地完成工作,但過度的壓力會直接影響我們工作的態度和情緒,這樣就間接導致了工作效率下降,因此管理好自己的情緒就顯得尤為重要,及時調整好情緒和狀態會事半功倍。
  • 作為入門基礎的C語言,怎麼自學?
    作為一門基礎語言,想學習編程卻又沒有基礎的朋友,C語言可以成為你入門打基礎的語言之一!那麼我們要怎麼學習它,打下堅實編程基礎呢?自學的話又要多久?編程學習是一條漫長路,哪怕是編程的大神,也不敢百分百保證不出問題,即便他們的技術已經是行業頂尖水平,仍然也在不斷的學習創新,所以作為小白新手,既然準備跨入編程的世界,那就要有一定的覺悟!
  • 鄒軍:一套完整的框架編程系統教程
    點擊上方,關注我哈
  • 我的R語言學習方法
    有朋自遠方來,不亦樂乎,並誠邀入群,以達相互學習和進步之美好心願。通過運營R語言公眾號,認識了各行各業在學習和使用R語言的朋友們。經常會遇到這些問題:我是R新手,要怎樣學習R語言?我要學習R語言,需要學習那些內容?王老師,R語言學習,有什麼書籍或者視頻推薦和分享嗎?等等。
  • 怎樣學習C語言(獻給迷茫的C愛好者)!
    各種嵌入式設備,如手機、PDA也都是C語言開發的。C語言歷史悠久,其內容也絕非譚浩強老師的《C語言程序設計(第二版)》所描寫敘述的那麼簡單——那本書僅僅是針對中國國情的教學入門書而已。    學生總喜歡問:那種語言好?學那種語言有前途?這樣的問題天天在討論,永無休止。
  • 【值得收藏】老外的牛逼編程技巧
    (Learn a new programming language)學習一門新的程式語言將有助於你開拓新的思維方式,特別是當你使用不熟悉的語言時,你將學習到很多種思維方法應用到語言中。而所學習到的新思維方式,你可以運用在你所熟知的語言中。甚至有時你會使用新學的語言進行你的重要項目。2.閱讀好的且具有挑戰性的編程書籍(Read a good, challenging programming book)從書中你將學到很多,雖然實踐很重要,但通過閱讀好的且具有挑戰性編程書籍是你改變思維方式重要的一步。
  • Wolfram語言入門
    同時,我們的在線文檔也在不斷擴充,隨著大量新範例在 2007年的引入,印刷版將達上萬頁.Wolfram|Alpha於2009年誕生,其獨特的自然語言界面無需任何說明或文檔.但接下來,對由Mathematica和Wolfram|Alpha融合而成的Wolfram語言,我們有必要再次給出文檔並對其進行說明.