這樣一段代碼在軟體工程界屬於什麼水平?

在 《The Go Programming Language》中看到的,求分析:

func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
// NOTE: ignoring potential errors from input.Err()
for line, n := range counts {
if n &> 1 {
fmt.Printf("%d %s
", n, line)
}
}
}


如果你能從這麼一段無腦的不超過二十行的代碼中看出一個人的編程水平,那我真是極其佩服。唯一能說的,就是他對沒有做err的處理做了一個注釋說明,不過這也沒有什麼好稱讚亦或吐槽的。

這麼一個簡單的O(n)統計輸入中重複行的代碼,實在沒有什麼必要有其它的實現方法。也因此,要我說,這是行業最次水平,也是最好水平。


實現這個邏輯,讓誰來寫都是一樣的,不論水平高低。go代碼都很直白,沒多少花式可以搞。

多人協作時,這樣的特性還是挺有益的。這樣的理念和 java 類似,但是沒有 java 羅嗦。


對應的C++11代碼:

int main()
{
std::unordered_map& counts;
std::string line;
while (getline(std::cin, line)) // Koenig Lookup
counts[line]++;
for (const auto item : counts)
{
if (item.second &> 1)
printf("%d %s
", item.second, item.first.c_str());
}
}


先說結論,這段代碼說明GO語言的運行時和標準庫在軟體工程界是一個極高的水平,然後這麼一段小程序:

var c = make(chan bool)
var d = make(chan bool)

func get(b []byte) {
close(c) //告訴另一個goroutine,我的實參已經傳進來了,可以去改了
&<-d //等待另一個goroutine改完 fmt.Println(b[3]) } func main(){ b := []byte {1,2,3,4,5,6,7,8} go func(b []byte) { &<-c //等待main函數所在的goroutine執行get傳入參數 b[3] = 8 close(d) //告訴main函數所在的goroutine我已經修改完畢 fmt.Println(b[3]) } (b) get(b[:4]) fmt.Println("Hello World") }

這份代碼的目的是說明,類似b[s:e]這種傳入參數,其實沒有經過拷貝,原因就是go的[]byte實際是一個 這樣的結構體:

type sliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}

一個指針指向真正的數據,兩個int說明切片內的元素數量和切片的容量。所以傳遞[]byte實質上只是傳了一個 指針,2個int,24位元組的數據。為什麼要說這個呢,因為在go的標準庫中大量使用這種語法:

// NewScanner returns a new Scanner to read from r.
// The split function defaults to ScanLines.
func NewScanner(r io.Reader) *Scanner {
return Scanner{
r: r,
split: ScanLines,
maxTokenSize: MaxScanTokenSize,
}
}

type Scanner struct {
r io.Reader // The reader provided by the client.
split SplitFunc // The function to split the tokens.
maxTokenSize int // Maximum size of a token; modified by tests.
token []byte // Last token returned by split.
buf []byte // Buffer used as argument to split.
start int // First non-processed byte in buf.
end int // End of data in buf.
err error // Sticky error.
empties int // Count of successive empty tokens.
scanCalled bool // Scan has been called; buffer is in use.
done bool // Scan has finished.
}

可以看到bufio.Scanner這個結構,他的buf和他的token都是[]byte,而且標準庫的內部實現中 都是無拷貝的操作。當然go的實現者不會像我們這些新手一樣,寫個Scan寫一堆拷貝,比如:

class Scanner {
private:
std::string buff;
std::string token;
public:
bool GetLine() {
// ...定位換行,處理CRLF和LF
token = buff.substr(s, e);
// ...
}
};

這樣就發生了拷貝。那麼到map這裡,原文中的代碼:

counts[input.Text()]++

input.Text()函數的原型是:

// Text returns the most recent token generated by a call to Scan
// as a newly allocated string holding its bytes.
func (s *Scanner) Text() string {
return string(s.token)
}

而string在go語言裡面是純粹的數組,[]byte到string的轉換是運行時做的:

// Buf is a fixed-size buffer for the result,
// it is not nil if the result does not escape.
func slicebytetostring(buf *tmpBuf, b []byte) string {
l := len(b)
if l == 0 {
// Turns out to be a relatively common case.
// Consider that you want to parse out data between parens in "foo()bar",
// you find the indices and convert the subslice to string.
return ""
}
if raceenabled l &> 0 {
racereadrangepc(unsafe.Pointer(b[0]),
uintptr(l),
getcallerpc(unsafe.Pointer(buf)),
funcPC(slicebytetostring))
}
if msanenabled l &> 0 {
msanread(unsafe.Pointer(b[0]), uintptr(l))
}
s, c := rawstringtmp(buf, l)
copy(c, b)
return s
}

這裡看出一個問題:string的轉化是有複製代價的,那麼每次往map中送數據的時候使用string是不是進行了好多次 拷貝呢?其實編譯器沒有那麼傻,編譯器有下面這個函數:

func slicebytetostringtmp(b []byte) string {
// Return a "string" referring to the actual []byte bytes.
// This is only for use by internal compiler optimizations
// that know that the string form will be discarded before
// the calling goroutine could possibly modify the original
// slice or synchronize with another goroutine.
// First such case is a m[string(k)] lookup where
// m is a string-keyed map and k is a []byte.
// Second such case is "&<"+string(b)+">" concatenation where b is []byte.
// Third such case is string(b)=="foo" comparison where b is []byte.

if raceenabled len(b) &> 0 {
racereadrangepc(unsafe.Pointer(b[0]),
uintptr(len(b)),
getcallerpc(unsafe.Pointer(b)),
funcPC(slicebytetostringtmp))
}
if msanenabled len(b) &> 0 {
msanread(unsafe.Pointer(b[0]), uintptr(len(b)))
}
return *(*string)(unsafe.Pointer(b))
}

可以避免臨時用的string也要複製的尷尬。那麼再說最後的for循環輸出,如果使用C++就得像@陳碩所說,使用 引用,避免無用的複製,但是go裡面,string本來就是一個類似數組指針的東西,也就直接可以像原文那樣循環了。

綜上可見,使用GO可以很容易寫出比較優秀的代碼,得益於強大的運行時和編譯器。聲明,以上所述標準庫和運行時 代碼均基於go1.7.1


實在不知道有什麼可以分析的地方。。。


施主我看你印堂發黑, 近日線上 BUG 必定不斷.

==================================================
==================================================
==================================================
============================#-====================
=========================== =# ===================
========= ================# # ==================
=========# # =============-# ###########=##====
======== # # === #### ##### # =# ==
======== # ##########===
========#- ###- #### ###=====
========# #### =======
======= # ## == --=====
======= # =#===============
======= # =#===### ========
=======## ### #== # ==========
=======## =# ## =============
====== # ### #===============
======-# #===============
======## #-==============
===== # #-==============
=====## # ==============
==== # # ==============

看相都比從 20 行代碼看水平靠譜.

吐槽完畢.

代碼還是中規中矩的, 簡單高效無花招. 但是,

counts := make(map[string]int)

這個 counts 變數命名我肯定是不喜歡的. 不過既然這段代碼出自大牛之手, 我也不好評價什麼. 努力朝大牛邁進吧 !


好很好以及非常好的水平O(≧▽≦)O

----------------------

你們為什麼要對書上的一段示例代碼這麼糾結。是不是別人拿出一個helloworld你們也要分析半天。


#=)=#+"%#=[]#=#%#$¥€???????????&>%##|}^||#||||¥¥$$[]

(有人教過我一個方法:當你對一個人徹底無語時,請使用以上表達形式)


說明

1. 涉及的業務很少。或者說,是功能性的代碼,不是業務性的代碼。

2. 函數很簡單,一個函數一個功能。main函數,說明只是一個小工具,要麼是不怎麼「人性化」的工具,要麼是功能很少的工具(說不「人性化」說的並不是在黑,而是他們的邏輯確實比較簡單,基本用不著考慮用戶體驗一類的東西)。

3. 非要扯到程度的話,bufio類型的庫,屬於中等難度的庫,stdin的概念則是比較難的了。NewScanner和Stdin結合,說明此人能夠運用duck type,熟練掌握該語言,而且非要定個範圍的話,新手寫不出來這麼清晰的代碼,庫用的沒這麼熟練。

第二點補充說明一下:

並非在說代碼質量不高,而是確實是如果有業務的話,例如不是從stdin讀取流,而是轉為從文件讀取數據(忽略管道操作),那麼理論上要多至少一個if判斷,代碼也會稍顯凌亂。

關於更多的撕逼,請到評論區。

可以參考這個。

對,題主口口聲聲說工程角度評論,那就讓你看看工程角度寫的代碼大概是什麼樣子的,而且是用不懂程序的人看得懂的語言描述的。

如何看待百度無人車, 三千多個場景,一萬多個if?

恩,評論區的 @唐生 同學也亮代碼了。

評論區吹的那麼厲害,我還以為你寫代碼不用if呢。

工程么,可不就是一段一段的if么,呵呵。

少一個if,要麼程序出問題,要麼就直接崩了,穩定性都沒了,談個鬼的用戶體驗。

代碼來自 go語言 共同功能 函數 封裝與繼承 問題 ?

數據沒取到,就是一個if。

if options.WithData != nil {

連接退出失敗,一個if

if options.OnExit != nil {

連接未成功,此處有個todo的if

n, err := conn.Read(buf)
// handle
_, _ = n, err

情況判斷,就是一個if。

if options.AfterSend != nil {

至於題主本人,你覺得一個連原文都看不懂的人,口口聲聲跟我談工程,有用么,呵呵。

先看懂代碼行不,半桶水到評論區噴,我都覺得丟人。

山田艹膩馬

求分析這段代碼的功能 和實現思路


推薦閱讀:

現在想再學習一門編程語言,應該選擇go還是python?
Golang 的並發與 Erlang、Scala、Node.js 和 Python 的並發模型相比有何特點?
為什麼用golang作為遊戲服務端的開發語言,它的並發性如何?golang有什麼優點?
golang的goroutine是如何實現的?
有哪些公司在用 Google 的 Go 語言?成熟度和 Erlang 比起來如何?

TAG:軟體工程 | 代碼 | Go語言 |