2011年7月27日 星期三

Clean Code: A Handbook of Agile Software Craftsmanship

代碼整潔之道


Bear在這裡買的
amazon的參考
-----------------------------------------------------------
作者:(美)馬丁|譯者:韓磊
出版社:人民郵電
ISBN:9787115216878
出版日期:2010/01/01
頁數:388
人民幣:RMB 59 元
-----------------------------------------------------------




Clean Code: A Handbook of Agile Software Craftsmanship

P.3(原文P.4)
我們都曾經說過有朝一日再回頭清理。當然,在那些日子裡,我們都沒聽過勒布朗(LeBlanc)法則:稍後等於永不(Later equals never)。

We’ve all said we’d go back and clean it up later. Of course, in those days we didn’t know LeBlanc’s law: Later equals never.

P.6(原文P.8)
窗戶破損了的建築讓人覺得似乎無人照管。於是別人也再不關心。他們放任窗戶繼續破損。最終自已也參加破壞活動,在外牆上塗鴉,任垃圾堆積。一扇破損的窗戶開辟了大廈走向傾頹的道路。

Pragmatic Dave Thomas and Andy Hunt said this a different way. They used the metaphor of broken windows.3 A building with broken windows looks like nobody cares about it. So other people stop caring. They allow more windows to become broken. Eventually they actively break them. They despoil the facade with graffiti and allow arbage to collect. One broken window starts the process toward decay.

P.7(原文P.8)
Bjarne以"整潔的代碼只做好一件事"結束論斷。

Bjarne closes with the assertion that clean code does one thing well. It is no accident that there are so many principles of software design that can be boiled down to this simple admonition. Writer after writer has tried to communicate this thought. Bad code tries to do too much, it has muddled intent and ambiguity of purpose. Clean code is focused. Each function, each class, each module exposes a single-minded attitude that remains entirely undistracted, and unpolluted, by the surrounding details.

P.9(原文P.11)
不要重覆代碼,只做一件事,表達力,小規模抽象。

Here, in a few short paragraphs, Ron has summarized the contents of this book. No duplication, one thing, expressiveness, tiny abstractions. Everything is there.

P.11(原文P.14)
你該明白。讀與寫花費時間的比例超過10:1。

You get the drift. Indeed, the ratio of time spent reading vs. writing is well over 10:1. We are constantly reading old code as part of the effort to write new code.

P.12(原文P.14)
如果每次簽入時,代碼都比簽出時乾淨,那麼代碼就不會腐壞。

If we all checked-in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanup doesn’t have to be something big. Change one variable name for the better, break up one function that’s a little too large, eliminate one small bit of duplication, clean up one composite if statement.

P.16(原文P.)
問題不在於代碼的簡潔度,而是在於代碼的模糊度:即上下文在代碼中未被明確體現的程度。

The problem isn’t the simplicity of the code but the implicity of the code (to coin a phrase): the degree to which the context is not explicit in the code itself. The code implicitly requires that we know the answers to questions such as:

P.18-19(原文P.21)
廢話是另一種沒意義的區分。假設你有一Product類。如果還有一個 ProductInfo 或 ProductData 類,那它們的名稱雖然不同,意思卻無區別。Info 和 Data 就像 a, an, the一樣,是意義含混的廢話。

Noise words are another meaningless distinction. Imagine that you have a Product class. If you have another called ProductInfo or ProductData, you have made the names different without making them mean anything different. Info and Data are indistinct noise words like a, an, and the.

P.20(原文P.22)
名稱長短應與其作用域大小相對應。

My personal preference is that single-letter names can ONLY be used as local variables inside short methods. The length of a name should correspond to the size of its scope [N5]. If a variable or constant might be seen or used in multiple places in a body of code, it is imperative to give it a search-friendly name. Once again compare

P.32(原文P.34)
函數的第一規則是要短小。第二條規則是還要更短小。

The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that. This is not an assertion that I can justify. I can’t provide any references to research that shows that very small functions are better. What I can tell you is that for nearly four decades I have written functions of all different sizes. I’ve written several nasty 3,000-line abominations. I’ve written scads of functions in the 100 to 300 line range. And I’ve written functions that were 20 to 30 lines long. What this experience has taught me, through long trial and error, is that functions should be very small.

P.33(原文P.35)
函數應該做一件事。做好這件事。只做這一件事。

FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY.

P.34(原文P.37)
換一種說法。我們想要這樣讀程序:程序就像是一系列 TO 起頭的段落,每一段都描述當前抽象層級,並引用位於下一抽象層級的後續 TO 起頭段落。

To say this differently, we want to be able to read the program as though it were a set of TO paragraphs, each of which is describing the current level of abstraction and referencing subsequent TO paragraphs at the next level down.

P.39(原文P.43)
如果函數看來需要兩個、三個或三個以上參數,就說明其中一些參數應該封裝成類了。

When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own. Consider, for example, the difference between the two following declarations:

P.43(原文P.46)
另一方面,如果使用異常替代返回錯誤代碼,錯誤處理代碼就能從主路徑代碼中分離出來,得到簡化。

On the other hand, if you use exceptions instead of returned error codes, then the error processing code can be separated from the happy path code and can be simplified:

P.44(原文P.47)
函數應該只做一件事。錯誤處理就是一件事。因此,處理錯誤的函數不該做其他事。這意味著(如上例所示)如果關鍵字 try 在某個函數中存在,它就該是這個函數的第一個單詞,而且在 catch/finally 代碼塊後面也不該有其它內容。

Functions should do one thing. Error handing is one thing. Thus, a function that handles errors should do nothing else. This implies (as in the example above) that if the keyword try exists in a function, it should be the very first word in the function and that there should be nothing after the catch/finally blocks.

P.50(原文P.54)
我為什麼要極力貶低注釋?因為注釋會撒謊。

Why am I so down on comments? Because they lie. Not always, and not intentionally, but too often. The older a comment is, and the farther away it is from the code it describes, the more likely it is to be just plain wrong. The reason is simple. Programmers can’t realistically maintain them.

代碼在變動,在演化。從這裡移到那里。彼此分離、重造又合到一處。很不幸,注釋並不總是隨之變動~不能總是跟著走。

Code changes and evolves. Chunks of it move from here to there. Those chunks bifurcate and reproduce and come together again to form chimeras. Unfortunately the comments don’t always follow them—can’t always follow them. And all too often the comments get separated from the code they describe and become orphaned blurbs of everdecreasing accuracy. For example, look what has happened to this comment and the line it was intended to describe:

P.50(原文P.54)
真實只在一處地方有:代碼。只有代碼能忠實地告訴你它做的事。那是唯一真正準確的信息來源。

Truth can only be found in one place: the code. Only the code can truly tell you what it does. It is the only source of truly accurate information. Therefore, though comments are sometimes necessary, we will expend significant energy to minimize them.

P.62(原文P.67)
如果你發現自已想標記右括號,其實應該做的是縮短函數。

Sometimes programmers will put special comments on closing braces, as in Listing 4-6. Although this might make sense for long functions with deeply nested structures, it serves only to clutter the kind of small and encapsulated functions that we prefer. So if you find yourself wanting to mark your closing braces, try to shorten your functions instead.
....
} //while
System.out.println("wordCount = " + wordCount);
System.out.println("lineCount = " + lineCount);
System.out.println("charCount = " + charCount);
} // try
catch (IOException e) {
System.err.println("Error:" + e.getMessage());
} //catch
}//main

P.64(原文P.69)
假如你一定要寫注釋,請確保它描述了離它最近的代碼。

If you must write a comment, then make sure it describes the code it appears near. Don’t offer systemwide information in the context of a local comment. Consider, for example, the javadoc comment below. Aside from the fact that it is horribly redundant, it also offers information about the default port. And yet the function has absolutely no control over what that default is. The comment is not describing the function, but some other, far distant part of the system. Of course there is no guarantee that this comment will be changed when the code containing the default is changed.

P.75(原文P.80)
除非有很好的理由,否則就不要把關係密切的概念放到不同的文件中。實際上,這也是避免使用 protected 變量的理由之一。

Concepts that are closely related should be kept vertically close to each other [G10]. Clearly this rule doesn’t work for concepts that belong in separate files. But then closely related concepts should not be separated into different files unless you have a very good reason. Indeed, this is one of the reasons that protected variables should be avoided.

P.78(原文P.83)
更好的做法是把它放在易於找到的位置,然後再傳遞到真實使用的位置。

As an aside, this snippet provides a nice example of keeping constants at the appropriate level [G35]. The "FrontPage" constant could have been buried in the getPageNameOrDefault function, but that would have hidden a well-known and expected constant in an inappropriately low-level function. It was better to pass that constant down from the place where it makes sense to know it to the place that actually uses it.

P.79(原文P.84)
概念相關。概念相關的代碼應該放到一起。相關性越強,彼此之間的距離就該越短。

Conceptual Affinity. Certain bits of code want to be near other bits. They have a certain conceptual affinity. The stronger that affinity, the less vertical distance there should be between them.

P.87(原文P.93)
將變量設置為私有(private)有一個理由:我們不想其他人依賴這些變量。

There is a reason that we keep our variables private. We don’t want anyone else to depend on them. We want to keep the freedom to change their type or implementation on a whim or an impulse. Why, then, do so many programmers automatically add getters and setters to their objects, exposing their private variables as if they were public?

P.91(原文P.98)
方法不應調用由任何函數返回的對象的方法。換言之,只跟朋友談話,不與陌生人談話。
下列代碼違反了得墨耳律(除了違反其他規則之外),因為它調用了 getOptions() 返回值的 getScratchDir() 函數,又調用了 getScratchDir() 返回值的 getAbsolutePath() 方法。

The method should not invoke methods on objects that are returned by any of the allowed functions. In other words, talk to friends, not to strangers.

The following code3 appears to violate the Law of Demeter (among other things) because it calls the getScratchDir() function on the return value of getOptions() and then calls getAbsolutePath() on the return value of getScratchDir().

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

P.93(原文P.100)
最為精練的數據結構,是一個只有公共變量、沒有函數的類。這種數據結構有時被稱為數據傳送對象,或DTO(Data Transfer Objects)。DTO是非常有用的結構,尤其是在與數據庫通信、或解析套接字傳遞的消息之類場景中。

The quintessential form of a data structure is a class with public variables and no functions. This is sometimes called a data transfer object, or DTO. DTOs are very useful structures, especially when communicating with databases or parsing messages from sockets, and so on. They often become the first in a series of translation stages that convert raw data in a database into objects in the application code.

P.96(原文P.104)
這類手段的問題在於,它們搞亂了調用者代碼。調用者必須在調用之後即刻檢查錯誤。不幸的是,這個步驟很容易被遺忘。所以,遇到錯誤時,最好拋出一個異常。調用代碼很整潔,其邏輯不會被錯誤處理搞亂。

The problem with these approaches is that they clutter the caller. The caller must check for errors immediately after the call. Unfortunately, it’s easy to forget. For this reason it is better to throw an exception when you encounter an error. The calling code is cleaner. Its logic is not obscured by error handling.

P.97(原文P.105)
異常的妙處之一是,它們在程序中定義了一個範圍。執行 try-catch-finally 語句中 try 部分的代碼時,你是在表明可隨時取消執行,並在 catch 語句中接續。

One of the most interesting things about exceptions is that they define a scope within your program. When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch.

所以,在編寫可能拋出異常的代碼時,最好先寫出 try-catch-finally 語句。這能幫你定義代碼的用戶應該期待什麼,無論 try 代碼塊中執行的代碼出什麼錯都一樣。

In a way, try blocks are like transactions. Your catch has to leave your program in a consistent state, no matter what happens in the try. For this reason it is good practice to start with a try-catch-finally statement when you are writing code that could throw exceptions. This helps you define what the user of that code should expect, no matter what goes wrong with the code that is executed in the try.

P.100(原文P.109)
實際上,將第三方 API 打包是個良好的實踐手段。

Wrappers like the one we defined for ACMEPort can be very useful. In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty. Wrapping also makes it easier to mock out third-party calls when you are testing your own code.

P.101(原文P.110)
這種手法叫做特例模式(SPECIAL CASE PATTERN [Fowler])。創建一個類或配置一個對象,用來處理特例。你來處理特例,客戶代碼就不用應付異常行為了。異常行為被封裝到特例對象中。

This is called the SPECIAL CASE PATTERN [Fowler]. You create a class or configure an object so that it handles a special case for you. When you do, the client code doesn’t have to deal with exceptional behavior. That behavior is encapsulated in the special case object.

P.102(原文P.111)
在方法中返回 null 值是糟糕的做法,但將 null 值傳遞給其他方法就更糟糕了。除非 API 要求你向它傳遞 null 值,否則就要盡可能避免傳遞 null 值。

Returning null from methods is bad, but passing null into methods is worse. Unless you are working with an API which expects you to pass null, you should avoid passing null in your code whenever possible.

P.126(原文P.136)
關於類的第一條規則是類應該短小。第二條規則是還要更短小。

The first rule of classes is that they should be small. The second rule of classes is that they should be smaller than that. No, we’re not going to repeat the exact same text from the Functions chapter. But as with functions, smaller is the primary rule when it comes to designing classes. As with functions, our immediate question is always “How small?”

P.158(原文P.171-172)
我們中的許多人認為,Kent Beck關於簡單設計的四條規則,對於創建具有良好設計的軟件有著莫大的幫助。

跟據Kent Beck所述,只要遵循以下規則,設計就能變得"簡單":
1.運行所有測試。
2.不可重覆。
3.表達了程序員的意圖。
4.盡可能減少類和方法的數量。

以上規則按其重要程度排列。

Many of us feel that Kent Beck’s four rules of Simple Design1 are of significant help in creating well-designed software.

According to Kent, a design is “simple” if it follows these rules:
1. Runs all the tests
2. Contains no duplication
3. Expresses the intent of the programmer
4. Minimizes the number of classes and methods

The rules are given in order of importance.

P.270(原文P.286)
過時、無關或不正確的注釋就是廢棄的注釋。注釋會很快過時。最好別編寫將被廢棄的注釋。如果發現廢棄的注釋,最好盡快更新或刪除掉。廢棄的注釋會遠離它們曾經描述的代碼,變成代碼中無關和誤導的浮島。

A comment that has gotten old, irrelevant, and incorrect is obsolete. Comments get old quickly. It is best not to write a comment that will become obsolete. If you find an obsolete comment, it is best to update it or get rid of it as quickly as possible. Obsolete comments tend to migrate away from the code they once described. They become floating islands of irrelevance and misdirection in the code.

P.271(原文P.287)
看到注釋掉的代碼,就刪除它!別擔心,源代碼控制系統還會記得它。如果有人真的需要,可以簽出較前的版本。別被它搞到死去活來。

When you see commented-out code, delete it! Don’t worry, the source code control system still remembers it. If anyone really needs it, he or she can go back and check out a previous version. Don’t suffer commented-out code to survive.

P.271(原文P.288)
函數的參數量應該少。沒參數最好,一個次之,兩個、三個再次之。三個以上的參數非常值得質疑,應堅決避免。

Functions should have a small number of arguments. No argument is best, followed by one, two, and three. More than three is very questionable and should be avoided with prejudice. (See “Function Arguments” on page 40.)

P.272(原文P.288)
遵循"最小驚異原則(The Principle of Least Surprise)",函數或類應該實現其他程序員有理由期待的行為。

Following “The Principle of Least Surprise,”2 any function or class should implement the behaviors that another programmer could reasonably expect. For example, consider a function that translates the name of a day to an enum that represents the day.

P.274(原文P.291)
將概念分解到基類和派生類的最普遍的原因是較高層級基類概念可以不依賴於較低層級派生類概念。這樣,如果看到基類提到派生類名稱,這可能發現了問題。通常來話,基類對派生類應該一無所知。

The most common reason for partitioning concepts into base and derivative classes is so that the higher level base class concepts can be independent of the lower level derivative class concepts. Therefore, when we see base classes mentioning the names of their derivatives, we suspect a problem. In general, base classes should know nothing about their derivatives.

P.275(原文P.292)
優秀的軟件開發人員學會限制類或模塊中暴露的接口數量。類中的方法越少越好。函數知道的變量愈少愈好。類擁有的實體變量愈少愈好。

Good software developers learn to limit what they expose at the interfaces of their classes and modules. The fewer methods a class has, the better. The fewer variables a function knows about, the better. The fewer instance variables a class has, the better.

隱藏你的數據。隱藏你的工具函數。隱藏你的常量和你的臨時變量。不要創建擁有大量方法或大量實體變量的類。不要為子類創建大量受保護變量和函數。盡力保持接口緊湊。通過限制信息來控制耦合度。

Hide your data. Hide your utility functions. Hide your constants and your temporaries. Don’t create classes with lots of methods or lots of instance variables. Don’t create lots of protected variables and functions for your subclasses. Concentrate on keeping interfaces very tight and very small. Help keep coupling low by limiting information.

P.275(原文P.292)
代碼的問題是過不久它就會發出臭味,時間愈久,味道就愈酸臭。這是因為,在設計改變時,死代碼不會隨之更新。它還能通過編譯,但並不會遵循較新的約定或規則。它編寫的時候,系統是另一番模樣。如果你找到死代碼,就體面地埋葬它,將它從系統中刪除掉。

The problem with dead code is that after awhile it starts to smell. The older it is, the stronger and sourer the odor becomes. This is because dead code is not completely updated when designs change. It still compiles, but it does not follow newer conventions or rules. It was written at a time when the system was different. When you find dead code, do the right thing. Give it a decent burial. Delete it from the system.


P.276(原文P.293)
一般來說,人為耦合是指兩個沒有直接目的之間的模塊的耦合。其根源是將變量、常量或函數不恰當地放在臨時方便的位置。這是種漫不經心的偷懶行為。

In general an artificial coupling is a coupling between two modules that serves no direct purpose. It is a result of putting a variable, constant, or function in a temporarily convenient, though inappropriate, location. This is lazy and careless.

花點時間研究應該在什麼地方聲明函數、常量和變量。不要為了方便隨手放置,然後置之不理。

Take the time to figure out where functions, constants, and variables ought to be declared. Don’t just toss them in the most convenient place at hand and then leave them there.

P.277(原文P.294)
有什麼比在函數調用末尾遇到一個false參數更為可憎的事情了。那個 false 是什麼意思?如果它是 true 會有什麼變化嗎?不僅是一個選擇算子(selector)參數的目的難以記住,每個選擇算子參數將多個函數綁到了一起。選擇算子參數只是一種避免把大函數切分為多個小函數的偷懶做法。

There is hardly anything more abominable than a dangling false argument at the end of a function call. What does it mean? What would it change if it were true? Not only is the purpose of a selector argument difficult to remember, each selector argument combines many functions into one. Selector arguments are just a lazy way to avoid splitting a large function into several smaller functions. Consider:

P.279(原文P.296)
通常應該傾向於選用非靜態方法。如果有疑問,就是用非靜態函數,如果的確需要靜態函數,確保沒機會打算讓它有多態行為。

In general you should prefer nonstatic methods to static methods. When in doubt, make the function nonstatic. If you really want a function to be static, make sure that there is no chance that you’ll want it to behave polymorphically

P.279(原文P.297)
如果你必須查看函數的實現(或文檔)才知道它是做什麼的,就該換個更好的函數名,或者重新安排功能代碼,放到有較好名稱的函數中。

If you have to look at the implementation (or documentation) of the function to know what it does, then you should work to find a better name or rearrange the functionality so that it can be placed in functions with better names.

P.281(原文P.299)
首先,多數人使用 switch 語句,因為它是最直接了當又有力的方案,而不是因為它適合當前情形。這給我們的啟發是在使用 switch 之前,先考慮使用多態。

First, most people use switch statements because it’s the obvious brute force solution, not because it’s the right solution for the situation. So this heuristic is here to remind us to consider polymorphism before using a switch.

P.283(原文P.301)
如果沒有if 或 while 語句的上下文,布林邏輯就難以理解。應該把解釋了條件意圖的函數抽離出來。

Boolean logic is hard enough to understand without having to see it in the context of an if or while statement. Extract functions that explain the intent of the conditional.

For example:

if (shouldBeDeleted(timer))

is preferable to

if (timer.hasExpired() && !timer.isRecurrent())

P.283(原文P.302)
否定式要比肯定式難明白一些。所以,盡可能將條件表示為肯定形式。

Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives. For example:

if (buffer.shouldCompact())

is preferable to

if (!buffer.shouldNotCompact())

P.286(原文P.304)
注意 level+1 出現了兩次。這是個應該封裝到名為 nextLevel 之類的變量中的邊界條件。

Boundary conditions are hard to keep track of. Put the processing for them in one place. Don’t let them leak all over the code. We don’t want swarms of +1s and -1s scattered hither and yon. Consider this simple example from FIT:

if(level + 1 < tags.length)
{
parts = new Parse(body, tags, level + 1, offset + endTag);
body = null;
}

Notice that level+1 appears twice. This is a boundary condition that should be encapsulated within a variable named something like nextLevel.

int nextLevel = level + 1;
if(nextLevel < tags.length)
{
parts = new Parse(body, tags, nextLevel, offset + endTag);
body = null;
}