@[TOC](代碼整潔之道Clean Code筆記)
在線閱讀:書棧網(wǎng):https://www.bookstack.cn/read/Clean-Code-zh/spilt.8.docs-ch1.md
每個章節(jié)都會做一個自己的總結(jié),并為這個章節(jié)打一個重要性的參考分?jǐn)?shù),滿分五星(僅個人的角度)。
如果想盡快的了解一些代碼的規(guī)范,最好看一下阿里代碼規(guī)范,idea也可以安裝阿里代碼規(guī)范插件。
阿里開發(fā)手冊是實踐,這本書本身更多的是作者理念引導(dǎo)。理念的引導(dǎo)必然少不了佐證、和一些看似冗余的語句,但這都是我們站在如今開發(fā)環(huán)境上的結(jié)果論,因為所有技術(shù)性文章都是具有很強(qiáng)的時效性。
第 1 章 Clean Code 整潔代碼(3星)
第一章主要介紹這本書的背景和意圖,以及總結(jié)下整潔代碼的理念
有的章節(jié)幾節(jié)可以直接越過,但是看完的話既知所以然,又知其然
?為什么要整潔的代碼
反證:糟糕代碼的壞處
團(tuán)隊中各司其職,代碼是代碼人應(yīng)有的責(zé)任,要主動維護(hù),去說服那些阻礙我們優(yōu)化的。。。
?什么叫做整潔代碼
每個人對于整潔的定義都不同,這里只是作者代表的一些理念,要學(xué)會自我思考
- 意圖明確:只做一件事,提高表達(dá)力
- 擴(kuò)展性:提早構(gòu)建小規(guī)模、簡單抽象
- 簡潔:減少重復(fù)代碼,包括盡量少的實體,比如類、方法、函數(shù)等
- 正確性:能通過所有測試
- 體現(xiàn)系統(tǒng)中的全部設(shè)計理念
- 讀與寫花費時間的比例超過 10:1,代碼閱讀重要性,要對自己的讀者負(fù)責(zé)
- 整潔代碼需要從點滴做起
成功的案例并不能讓你成為成功者,只能分享給別人成功的過程,技巧
第 2 章 Meaningful Names 有意義的命名(3星)
語義明確,語境明確,不冗余
語義明確:最好通過變量名講解變量的意義,而不是注釋。例如:魔法值,就是不能明確意義的常量
避免誤導(dǎo):例如:縮寫不明確;專有名詞同名;命名類型不準(zhǔn)確;相似命名放一起區(qū)分,明確注釋;相似數(shù)字字母不明確
有意義的區(qū)分,廢話都是冗余。比如:Table 一詞永遠(yuǎn)不應(yīng)當(dāng)出現(xiàn)在表名中
使用可以讀的出來的名稱
使用可以搜索的名字:單字母名稱和數(shù)字常量僅用在短方法中的本地變量,最好不要用
避免思維映射:不要你覺得,別人也會這樣覺得
類名和對象名應(yīng)該是名詞或名詞短語,不應(yīng)當(dāng)是動詞
方法名應(yīng)當(dāng)是動詞或動詞短語
重載構(gòu)造器時,使用描述了參數(shù)的靜態(tài)工廠方法名。例如,
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
通常好于
Complex fulcrumPoint = new Complex(23.0);
可以考慮將相應(yīng)的構(gòu)造器設(shè)置為 private,強(qiáng)制使用這種命名手段。
別用雙關(guān)語
使用程序員熟悉的術(shù)語,或者所涉問題領(lǐng)域命名
添加有意義的語境:對字段進(jìn)行補(bǔ)充說明
第 3 章 Functions 函數(shù)(3星)
本章所講述的是有關(guān)編寫良好函數(shù)的機(jī)制
函數(shù)的第一規(guī)則是要短小
函數(shù)應(yīng)該只做好這一件事
每個函數(shù)一個抽象層級
讓代碼讀起來像是一系列自頂向下的 TO 起頭段落是保持抽象層級協(xié)調(diào)一致的有效技巧
- switch
利用多態(tài)來實現(xiàn),確保每個 switch 都埋藏在較低的抽象層級,而且永遠(yuǎn)不重復(fù)
例子:所有的員工都有一樣的流程,是否發(fā)薪日、計算薪水、發(fā)薪水,但是不同類型的員工具體流程動作不一樣
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
多態(tài)實現(xiàn):更改之后再增加職員類型,每種職工只需要做自己的事情,不需要所有的類型當(dāng)方法都更改,只需更改實現(xiàn)工廠實現(xiàn)類
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
-----------------
public class EmployeeFactoryImpl implements
EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch (r.type) {
case COMMISSIONED:
return new CommissionedEmployee(r);
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmploye(r);
default:
throw new InvalidEmployeeType(r.type);
}
}
}
- 使用描述性的名稱
別害怕長名稱、別害怕花時間取名字、命名方式要保持一致
- 函數(shù)參數(shù): 最理想的參數(shù)數(shù)量是零
- 無副作用函數(shù):不要把相關(guān)性不強(qiáng)的邏輯放進(jìn)來,強(qiáng)調(diào)復(fù)用性
- 分隔指令與詢問:避免混亂
- 使用異常替代返回錯誤碼
if (deletePage(page) == E_OK) { if (registry.deleteReference(page.name) == E_OK) { if (configKeys.deleteKey(page.name.makeKey()) == E_OK) { logger.log("page deleted"); } else { logger.log("configKey not deleted"); } } else { logger.log("deleteReference from registry failed"); }} else { logger.log("delete failed"); return E_ERROR;}
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:
另一方面,如果使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑代碼中分離出來,得到簡化:
復(fù)制代碼try { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey());} catch (Exception e) { logger.log(e.getMessage());}
- 重復(fù)可能是軟件中一切邪惡的根源
- 好的代碼需要慢慢打磨,精簡,優(yōu)化(這正是我喜歡的過程,樂此不疲)
第 4 章 Comments 注釋(2星)
作者畢竟站在英語母語的基礎(chǔ)上,還是要考慮下我們自己的環(huán)境
- 注釋不能美化糟糕的代碼,注釋不能成為糟糕代碼的發(fā)言人,代碼才是核心
- 別誤導(dǎo),別廢話,適時整理TODO、注釋的代碼塊
第 5 章 Formatting 格式 (1星)
幾乎不用看
- 垂直、橫向格式:代碼縮進(jìn)
第 6 章 Objects and Data Structures 對象和數(shù)據(jù)結(jié)構(gòu)(4星)
對象曝露行為,隱藏數(shù)據(jù)。便于添加新對象類型而無需修改既有行為,同時也難以在既有對象中添加新行為。
數(shù)據(jù)結(jié)構(gòu)曝露數(shù)據(jù),沒有明顯的行為。便于向既有數(shù)據(jù)結(jié)構(gòu)添加新行為,同時也難以向既有函數(shù)添加新數(shù)據(jù)結(jié)構(gòu)。
- 數(shù)據(jù)抽象:數(shù)據(jù)封裝,隱藏具體行為
- 數(shù)據(jù)、對象的反對稱性
過程式代碼(使用數(shù)據(jù)結(jié)構(gòu)的代碼)便于在不改動既有數(shù)據(jù)結(jié)構(gòu)的前提下添加新函數(shù)。面向?qū)ο蟠a便于在不改動既有函數(shù)的前提下添加新類。
- 得墨忒耳律
方法不應(yīng)調(diào)用由任何函數(shù)返回的對象的方法
下列代碼違反了得墨忒耳律(除了違反其他規(guī)則之外),因為它調(diào)用了 getOptions( )返回值的 getScratchDir( )函數(shù),又調(diào)用了 getScratchDir( )返回值的 getAbsolutePath( )方法。
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
第 7 章 Error Handling 錯誤處理(4星)
在本章中,要列出編寫既整潔又強(qiáng)固的代碼——優(yōu)雅地處理錯誤代碼的一些技巧和思路
整潔代碼是可讀的,但也要強(qiáng)固??勺x與強(qiáng)固并不沖突。如果將錯誤處理隔離看待,獨立于主要邏輯之外,就能寫出強(qiáng)固而整潔的代碼
- 使用異常而非返回碼,用統(tǒng)一異常處理處理異常
- 在編寫可能拋出異常的代碼時, 先寫 Try-Catch-Finally 語句
- 使用不可控異常,可控異常的代價就是違反開放/閉合原則
- 給出異常發(fā)生的環(huán)境說明
- 依調(diào)用者需要定義異常類
我們在應(yīng)用程序中定義異常類時,最重要的考慮應(yīng)該是它們?nèi)绾伪徊东@,然后根據(jù)捕獲規(guī)律去優(yōu)化異常捕獲
- 別返回 null 值
- 別傳遞 null 值
第 9 章 Unit Tests 單元測試(3星)
- 保持測試整潔,測試代碼和生產(chǎn)代碼一樣重要
- 整潔的測試,最重要的要素是可讀性
- 每個測試一個斷言
- 測試應(yīng)該有以下規(guī)則:快速、獨立、可重復(fù)、自足驗證、及時
第 10 章 Classes 類(5星)
代碼組織的更高層面——Classes
- 將系統(tǒng)的構(gòu)造與使用分開
- 類的組織
遵循標(biāo)準(zhǔn)的 Java 約定,類應(yīng)該從一組變量列表開始。如果有公共靜態(tài)常量,應(yīng)該先出現(xiàn)。然后是私有靜態(tài)變量,以及私有實體變量。很少會有公共變量。公共函數(shù)應(yīng)跟在變量列表之后。封裝
- 類應(yīng)該短小
對于函數(shù),我們通過計算代碼行數(shù)衡量大小。對于類,我們采用不同的衡量方法,計算權(quán)責(zé)(responsibility)。類的名稱應(yīng)當(dāng)描述其權(quán)責(zé),從命名開始規(guī)范。
單一權(quán)責(zé)原則(SRP)認(rèn)為,類或模塊應(yīng)有且只有一條加以修改的理由。系統(tǒng)應(yīng)該由許多短小的類而不是少量巨大的類組成。每個小類封裝一個權(quán)責(zé),只有一個修改的原因,并與少數(shù)其他類一起協(xié)同達(dá)成期望的系統(tǒng)行為。
內(nèi)聚:類應(yīng)該只有少量實體變量。類中的每個方法都應(yīng)該操作一個或多個這種變量。通常而言,方法操作的變量越多,就越黏聚到類上。
擴(kuò)展性:需求會改變,所以代碼也會改變。具體類包含實現(xiàn)細(xì)節(jié)(代碼),而抽象類則只呈現(xiàn)概念。依賴于具體細(xì)節(jié)的客戶類,當(dāng)細(xì)節(jié)改變時,就會有風(fēng)險。我們可以借助接口和抽象類來隔離這些細(xì)節(jié)帶來的影響。依賴倒置原則(Dependency Inversion Principle,DIP),DIP 認(rèn)為類應(yīng)當(dāng)依賴于抽象而不是依賴于具體細(xì)節(jié)。
單一權(quán)責(zé)和內(nèi)聚都是程度值,保證的他們平衡,邏輯內(nèi)聚,權(quán)責(zé)解耦,這并不簡單,SRP也充分考慮到了代碼擴(kuò)展性。
第 11 章 Systems 系統(tǒng)(5星)
本章將討論如何在較高的抽象層級——系統(tǒng)層級——上保持整潔
無論是設(shè)計系統(tǒng)或單獨的模塊,別忘了使用大概可工作的最簡單方案。
將系統(tǒng)的構(gòu)造與使用分開(編譯和運(yùn)行,java的環(huán)境中Spring通過依賴注入(Dependency Injection,DI),控制反轉(zhuǎn)(Inversion of Control,IoC)已經(jīng)幫我們把這件事做了)。延后初始化的好處:這種手段在 DI 中也有其作用。首先,多數(shù) DI 容器在需要對象之前并不構(gòu)造對象。其次,許多這類容器提供調(diào)用工廠或構(gòu)造代理的機(jī)制,而這種機(jī)制可為延遲賦值或類似的優(yōu)化處理所用。
**AOP: **在 AOP 中,被稱為方面(aspect)的模塊構(gòu)造指明了系統(tǒng)中哪些點的行為會以某種一致的方式被修改,從而支持某種特定的場景。這種說明是用某種簡潔的聲明或編程機(jī)制來實現(xiàn)的。
代理:使用代理,代碼量和復(fù)雜度是代理的兩大弱點,創(chuàng)建整潔代碼變得很難!另外,代理也沒有提供在系統(tǒng)范圍內(nèi)指定執(zhí)行點的機(jī)制,而那正是真正的 AOP 解決方案所必須的
純凈的 Java AOP 框架:Bean工廠內(nèi),每個 bean 就像是嵌套“俄羅斯套娃”中的一個,每個由數(shù)據(jù)存取器對象(DAO)代理(包裝)的 Bank 都有個域?qū)ο?,?bean 本身又是由 JDBC 驅(qū)動程序數(shù)據(jù)源代理。通過XML/注解的方式減少對代碼的入侵,只留下純POJO。
AspectJ ASPECTS AspectJ 的方面:AspectJ 卻提供了一套用以切分關(guān)注面的豐富而強(qiáng)有力的工具。
測試驅(qū)動系統(tǒng)架構(gòu):大設(shè)計(Big Design Up Front,BDUF)——系統(tǒng)架構(gòu)。最佳的系統(tǒng)架構(gòu)由模塊化的關(guān)注面領(lǐng)域組成,每個關(guān)注面均用純 Java(或其他語言)對象實現(xiàn)。不同的領(lǐng)域之間用最不具有侵害性的方面或類方面工具整合起來。這種架構(gòu)能測試驅(qū)動,就像代碼一樣。
優(yōu)化決策:模塊化和關(guān)注面切分成就了分散化管理和決策。擁有模塊化關(guān)注面的 POJO 系統(tǒng)提供的敏捷能力,允許我們基于最新的知識做出優(yōu)化的、時機(jī)剛好的決策。決策的復(fù)雜性也降低了。
選擇合適的架構(gòu)——標(biāo)準(zhǔn)
系統(tǒng)需要領(lǐng)域特定語言:領(lǐng)域特定語言(Domain-Specific Language,DSL)。DSL 是一種單獨的小型腳本語言或以標(biāo)準(zhǔn)語言寫就的 API,領(lǐng)域?qū)<铱梢杂盟帉懽x起來像是組織嚴(yán)謹(jǐn)?shù)纳⑽囊话愕拇a。領(lǐng)域特定語言允許所有抽象層級和應(yīng)用程序中的所有領(lǐng)域,從高級策略到底層細(xì)節(jié),使用 POJO 來表達(dá)。
第 12 章 Emergence 迭進(jìn)
本章中寫到的實踐來自于本書作者數(shù)十年經(jīng)驗的精練總結(jié)。遵循簡單設(shè)計的實踐手段,開發(fā)者不必經(jīng)年學(xué)習(xí)就能掌握好的原則和模式。
提升內(nèi)聚性,降低耦合度,切分關(guān)注面,模塊化系統(tǒng)性關(guān)注面,縮小函數(shù)和類的尺寸,選用更好的名稱,如此等等。這也是應(yīng)用簡單設(shè)計后三條規(guī)則的地方:消除重復(fù),保證表達(dá)力,盡可能減少類和方法的數(shù)量。
通過迭進(jìn)設(shè)計達(dá)到整潔目的,Kent Beck 關(guān)于簡單設(shè)計的四條規(guī)則,據(jù) Kent 所述,只要遵循以下規(guī)則,設(shè)計就能變得“簡單”,以下規(guī)則按其重要程度降序排列:
- 運(yùn)行所有測試;
- 不可重復(fù);
- 表達(dá)了程序員的意圖;
- 盡可能減少類和方法的數(shù)量;
第 13 章 Concurrency 并發(fā)編程(5星)
“對象是過程的抽象。線程是調(diào)度的抽象?!? ——James O
這個章節(jié)主要講述了并發(fā)編程的來源、優(yōu)勢和劣勢,以及如何避免、解決并發(fā)錯誤的方法和方向
? 為什么要并發(fā)
并發(fā)是一種解耦策略。
解耦目的與時機(jī)能明顯地改進(jìn)應(yīng)用程序的吞吐量和結(jié)構(gòu)。
并發(fā)會在性能和編寫額外代碼上增加一些開銷;
正確的并發(fā)是復(fù)雜的,即便對于簡單的問題也是如此;
并發(fā)缺陷并非總能重現(xiàn),所以常被看做偶發(fā)事件而忽略,未被當(dāng)做真的缺陷看待;
并發(fā)常常需要對設(shè)計策略的根本性修改。
并發(fā)防御原則
- 單一權(quán)責(zé)原則
單一權(quán)責(zé)原則(SRP)認(rèn)為,方法/類/組件應(yīng)當(dāng)只有一個修改的理由。并發(fā)設(shè)計自身足夠復(fù)雜到成為修改的理由,所以也該從其他代碼中分離出來。不幸的是,并發(fā)實現(xiàn)細(xì)節(jié)常常直接嵌入到其他生產(chǎn)代碼中。
需要考慮的問題:
并發(fā)相關(guān)代碼有自己的開發(fā)、修改和調(diào)優(yōu)生命周期;
開發(fā)相關(guān)代碼有自己要對付的挑戰(zhàn),和非并發(fā)相關(guān)代碼不同,而且往往更為困難;
即便沒有周邊應(yīng)用程序增加的負(fù)擔(dān),寫得不好的并發(fā)代碼可能的出錯方式數(shù)量也已經(jīng)足具挑戰(zhàn)性。
建議:分離并發(fā)相關(guān)代碼與其他代碼。
- 限制數(shù)據(jù)作用域
兩個線程修改共享對象的同一字段時,可能互相干擾,導(dǎo)致未預(yù)期的行為。解決方案之一是采用 synchronized 關(guān)鍵字在代碼中保護(hù)一塊使用共享對象的臨界區(qū)(critical section)。限制臨界區(qū)的數(shù)量很重要。更新共享數(shù)據(jù)的地方越多,就越可能:
謹(jǐn)記數(shù)據(jù)封裝;嚴(yán)格限制對可能被共享的數(shù)據(jù)的訪問。
避免共享數(shù)據(jù)的好方法之一就是一開始就避免共享數(shù)據(jù)。
線程應(yīng)盡可能地獨立,讓每個線程在自己的世界中存在,不與其他線程共享數(shù)據(jù)。
了解 Java 庫
學(xué)習(xí)類庫,了解基本算法。理解類庫提供的與基礎(chǔ)算法類似的解決問題的特性。
了解執(zhí)行模型
學(xué)習(xí)這些基礎(chǔ)算法,理解其解決方案。
- Producer-Consumer 生產(chǎn)者-消費者模型
- Readers-Writers 讀者-作者模型
- Dining Philosophers 哲學(xué)家用餐模式
警惕同步方法之間的依賴
- 避免使用一個共享對象的多個方法
- 有時必須使用一個共享對象的多個方法,有 3 種應(yīng)對手段:
- 基于客戶端的鎖定——客戶端代碼在調(diào)用第一個方法前鎖定服務(wù)端,確保鎖的范圍覆蓋了調(diào)用最后一個方法的代碼;
- 基于服務(wù)端的鎖定——在服務(wù)端內(nèi)創(chuàng)建鎖定服務(wù)端的方法,調(diào)用所有方法,然后解鎖。讓客戶端代碼調(diào)用新方法;
- 適配服務(wù)端——創(chuàng)建執(zhí)行鎖定的中間層。這是一種基于服務(wù)端的鎖定的例子,但不修改原始服務(wù)端代碼。
盡可能減小同步區(qū)域
盡早考慮關(guān)閉問題,盡早令其工作正常。
測試線程代碼
編寫有潛力曝露問題的測試,在不同的編程配置、系統(tǒng)配置和負(fù)載條件下頻繁運(yùn)行。如果測試失敗,跟蹤錯誤。別因為后來測試通過了后來的運(yùn)行就忽略失敗。
有一大堆問題要考慮。下面是一些精練的建議:
- 將偽失敗看作可能的線程問題,不要將系統(tǒng)錯誤歸咎于偶發(fā)事件
- 先使非線程代碼可工作, 不要同時追蹤非線程缺陷和線程缺陷。確保代碼在線程之外可工作。
- 編寫可插拔的線程代碼,這樣就能在不同的配置環(huán)境下運(yùn)行。
- 編寫可調(diào)整的線程代碼,要獲得良好的線程平衡,常常需要試錯。一開始,在不同的配置環(huán)境下監(jiān)測系統(tǒng)性能。要允許線程數(shù)量可調(diào)整。在系統(tǒng)運(yùn)行時允許線程發(fā)生變動。允許線程依據(jù)吞吐量和系統(tǒng)使用率自我調(diào)整。
- 運(yùn)行多于處理器數(shù)量的線程,系統(tǒng)在切換任務(wù)時會發(fā)生一些事。為了促使任務(wù)交換的發(fā)生,運(yùn)行多于處理器或處理器核心數(shù)量的線程。任務(wù)交換越頻繁,越有可能找到錯過臨界區(qū)或?qū)е滤梨i的代碼。
- 在不同平臺上運(yùn)行
- 裝置試錯代碼,并強(qiáng)迫錯誤發(fā)生:有兩種裝置代碼的方法:硬編碼、自動化
第 15 章 JUnit Internals JUnit 內(nèi)幕(2星)
本章介紹了JUnit的一些簡單的模塊
第 16 章 重構(gòu) SerialDate(4星)
本章詳解對 org.jfree.date庫中的SerialDate日期類進(jìn)行重構(gòu),簡化的過程。增加了測試覆蓋率,修復(fù)了一些錯誤,澄清并縮小了代碼。
第 17 章 味道與啟發(fā)(3星)
本章又列舉了作者之前列出過的,一些不好的習(xí)慣,并把這些比作難聞的氣味
干凈的代碼不是通過遵循一組規(guī)則來編寫的。
附錄 A 并發(fā)編程 II(4星)
并發(fā)編程的一些擴(kuò)充信息,多了很多的示例講解
在本章中,我們談到并發(fā)更新,還有清理及避免同步的規(guī)程。我們談到線程如何提升與 I/O 有關(guān)的系統(tǒng)的吞吐量,展示了獲得這種提升的整潔技術(shù)。我們談到死鎖及干凈地避免死鎖的規(guī)程。最后,我們談到通過裝置代碼暴露并發(fā)問題的策略。
- 死鎖
死鎖的發(fā)生需要 4 個條件:
互斥:無法在同一時間為多個線程所用;數(shù)量上有限制
這種資源的常見例子是數(shù)據(jù)庫連接、打開后用于寫入的文件、記錄鎖或是信號量。
上鎖及等待:當(dāng)某個線程獲取一個資源,在獲取到其他全部所需資源并完成其工作之前,不會釋放這個資源。
無搶先機(jī)制:線程無法從其他線程處奪取資源。一個線程持有資源時,其他線程獲得這個資源的唯一手段就是等待該線程釋放資源。
循環(huán)等待:這也被稱為“死命擁抱”。想象兩個線程,T1 和 T2,還有兩個資源,R1 和 R2。T1 擁有 R1,T2 擁有 R2。T1 需要 R2,T2 需要 R1。
這 4 種條件都是死鎖所必需的。只要其中一個不滿足,死鎖就不會發(fā)生。
避免死鎖的一種策略是規(guī)避互斥條件。你可以:
- 使用允許同時使用的資源;
- 增加資源數(shù)量,使其等于或大于競爭線程的數(shù)量;
- 在獲取資源之前,檢查是否可用。
- 不上鎖及等待
- 滿足搶先機(jī)制
- 不做循環(huán)等待
將解決方案中與線程相關(guān)的部分分隔出來,再加以調(diào)整和試驗,是獲得判斷最佳策略所需的洞見的正道。
總結(jié)
干凈有經(jīng)驗值,也有固定分,不是通過遵循一組規(guī)則來編寫的,需要的是迭進(jìn),不需要鉆牛角尖。
讀英文原文的時候突然想到:英語大多是結(jié)果論,喜歡陳述事實,就好像罪犯的對白