ode does', but that is just a small part of it.The purpose of 買粉絲menting is to help the reader know as much as the writer did. -- Dustin Boswell《The Art of Readable Code》 譯:你可能以為注釋的目的是“解釋代碼做了什么”,但這只是其中很小一部分,注釋的目的是盡量幫助讀者了解得和作者一樣多
如同 John Ousterhout 教授一樣,The Art of Readable Code 的作者 Dustin Boswell,也是一個堅定的注釋支持者。與 Robert C.Martin 類似,Dustin Boswell 同樣認為我們不應該為那些從代碼本身就能快速推斷的事實寫注釋,并且他也反對拐杖式注釋,注釋不能美化代碼。
但 Dustin Boswell 認為注釋的目的不僅解釋了代碼在做什么,甚至這只是一小部分,注釋最重要的目的是幫助讀者了解得和作者一樣多 。編寫注釋時,我們需要站在讀者的角度,去想想他們知道什么,這是注釋的核心。這里有非常多的空間是代碼很難闡述或無法闡述的,配上注釋的代碼并非就是糟糕的代碼,相反有些時候,注釋還是好代碼最棒的僚機。
01 更精準表述
There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton 譯:計算機科學中只有兩個難題:緩存失效和命名Martin Fowler 在他的 TwoHardThings 文章中引用了 Phil Karlton 的一段話,命名一直都是一件非常難的事情,因為我們需要將所有含義濃縮到幾個單詞中表達。很早之前學 Java,接觸到很長的類名是 ClassPathXmlApplicationContext。可能有人認為只要能將含義準確地表達出來,名字長一些無所謂。那如果我們需要有一段處理有關“一帶一路”的內容,那我們的代碼可能是這樣的:
public class TheSilkRoadE買粉絲nomicBeltAndThe21stCenturyMaritimeSilkRoad {
他非常準確的表達了含義,但很明顯這不是我們期望的代碼。但如果我們輔以簡單的注釋,代碼會非常清晰,說明了簡稱,也說明了全意,表述更精準。
/
*** 一帶一路
* 絲綢之路經濟帶和21世紀海上絲綢之路
*/
public class OneBeltOneRoad {
02 代碼層次切割
函數抽取是我們經常使用且成本最低的重構方法之一,但并非銀彈。函數并非抽得越細越好,如同分布式系統中,并非無限的堆機器讓每臺機器處理的數據越少,整體就會越快。過深的嵌套封裝,會加大我們的代碼閱讀成本,有時我們只需要有一定的層次與結構幫助我們理解就夠了,盲目的抽取封裝是無意義的。
/
*** 客戶列表查詢
*/
public List queryCustomerList(){
// 查詢參數準備
UserInfo userInfo = 買粉絲ntext.getLoginContext().getUserInfo();
if(userInfo == null || StringUtils.isBlank(userInfo.getUserId())){
return Collections.emptyList();
}
LoginDTO loginDTO = userInfoConvertor.買粉絲nvertUserInfo2LoginDTO(userInfo);
// 查詢客戶信息
List customerSearchList = customerRemoteQueryService.query(loginDTO);
Iterable it = customerSearchList.iterator();
// 排除不合規客戶
while(it.hasNext()){
CustomerSearchVO customerSearchVO = it.next();
if(isInBlackList(customerSearchVO) || isLowQuality(customerSearchVO)){
it.remove();
}
}
// 補充客戶其他屬性信息
batchFillCustomerPositionInfo(customerSearchList);
batchFillCustomerAddressInfo(customerSearchList);
}
其實細看每一處代碼,都很容易讓人理解。但如果是一版沒有注釋的代碼,可能我們會有點頭疼。缺少結構缺少分層,是讓我們大腦第一感觀覺得它很復雜,需要一次性消化多個內容。通過注釋將代碼層次進行切割,是一次抽象層次的劃分。同時也不建議大家不斷去抽象私有方法,這樣代碼會變得非常割裂,并且上下文的背景邏輯、參數的傳遞等等,都會帶來額外的麻煩。
03 母語的力量
其實上述例子,我們更易閱讀,還有一個重要的原因,那就是母語的力量。我們天然所經歷的環境與我們每天所接觸到的事物,讓我們對中文與英文有完全不一樣的感受。我們代碼的編寫本質上是一個將我們溝通中的“中文問題”,翻譯成“英文代碼”來實現的過程。而閱讀代碼的人在做得,是一件將“英文代碼”翻譯成“中文表述”的事情。而這之中經過的環節越多,意思變味越嚴重。
TaskDispatch taskDispatch = TaskDispatchBuilder.newBuilder().withExceptionIgnore().build();
taskDispatch
// 外貿信息
.join(new FillForeignTradeInfoTask(targetCustomer, sourceInfo))
// 國民經濟行業、電商平臺、注冊資本
.join(new FillCustOutterInfoTask(targetCustomer, sourceInfo))
// 客戶信息
.join(new FillCustomerOriginAndCategoryTask(targetCustomer, sourceInfo))
// 客戶擴展信息
.join(new FillCustExtInfoTask(targetCustomer, sourceInfo))
// 收藏屏蔽信息
.join(new FillCollectStatusInfoTask(targetCustomer, sourceInfo, loginDTO()))
// 詳情頁跳轉需要的標簽信息
.join(new FillTagInstanceTask(targetCustomer, sourceInfo, loginDTO()))
// 客戶信息完整度分數
.join(new FillCustomerS買粉絲reTask(targetCustomer, sourceInfo))
// 潛客分層完整度
.join(new FillCustomerSegmentationTask(targetCustomer, sourceInfo))
// 填充操作信息
.join(new FillOperationStatusTask(targetCustomer, sourceInfo, loginDTO))
// 認證狀態
.join(new FillAvStatusTask(targetCustomer, loginDTO))
// 客戶地址和組織
.join(new FillCompanyAddressTask(targetCustomer, loginDTO))
// 違規信息
.join(new FillPunishInfoTask(targetCustomer, sourceInfo))
// 填充客戶黑名單信息
.join(new FillCustomerBlackStatusTask(targetCustomer, sourceInfo))
// 填充客戶意愿度
.join(new FillCustIntentionLevelTask(targetCustomer, sourceInfo));
// 執行
.execute();
這是一段補齊客戶全數據信息的代碼,雖然每一個英文我們都看得懂,但我們永遠只會第一眼去看注釋,就因為它是中文。并且也因為有這些注釋,這里非常復雜的業務邏輯,我們同樣可以非常清晰的了解到它做了哪些,分哪幾步,如果要優化應該如何處理。這里也建議大家寫中文注釋,注釋是一種說明,越直觀越好,中文的親和力是英文無法比擬的。當然,這條建議并不適合美國程序員。
07 注釋的真正歸屬
01 復雜的業務邏輯
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !買粉絲ntainsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args ->
2024-07-24 11:20
2024-07-24 11:10
2024-07-24 10:29
2024-07-24 10:21
2024-07-24 09:44
2024-07-24 09:41