0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

河套IT TALK——TALK 12:編程的技術(shù)|藝術(shù)|術(shù)術(shù) 下篇:對著代碼解讀編程的哲學(xué)

共熵服務(wù)中心 ? 來源:未知 ? 2022-12-16 19:35 ? 次閱讀



往期回顧

TALK 6:編程的技術(shù)|藝術(shù)|術(shù)術(shù)(上篇)骨灰級程序員的心路歷程

TALK 10:編程的技術(shù)|藝術(shù)|術(shù)術(shù)(中篇)編程的思想、藝術(shù)和哲學(xué)
前期回顧

前面兩篇里,骨灰級程序員梁峻墅給大家介紹了他的心路歷程,他談了程序員文化和武林文化的理解,將編程與孫子兵法對照,闡釋編程的藝術(shù)性表達(dá)以及哲學(xué)思考。本篇將不再務(wù)虛,而是直接上代碼,讓梁老師帶著你解讀牛逼代碼的高明之處。

一段黑客的代碼

務(wù)虛的事都講完了,現(xiàn)在得真的要講講務(wù)實的事了。前面講的那些是武功秘籍的目錄,而真正的武功秘籍在代碼里。實踐出真知,只有虛實結(jié)合,才能感同身受。

我找了一段Zero-Day(編者注:下文簡稱0day)組織幾乎每個程序都要用到的一段代碼作為示例。

0day,用過盜版軟件的朋友應(yīng)該都很熟悉,它是全球最牛B的盜版組織,里面高手如云,都是Richard Stallman的追隨者。任何一個被他們盯上的大廠軟件,只要敢早上發(fā)布,中午的發(fā)布會招待宴還沒吃完,破解版就已經(jīng)在各大盜版網(wǎng)站上可以下載了,平均破解時間就是兩三個小時,承諾破解時間不超過24小時,所以叫0day,當(dāng)天解決,童叟無欺。我們就來看看這些全球頂尖黑客是怎么寫代碼的。

我找的這段代碼的功能很簡單,就是一個基于文件的記錄日志類,其C++版本加上頭文件,總代碼行數(shù)不超過200行,而核心代碼不到100行,但就在這方寸之間,隱藏著十一個戰(zhàn)術(shù)思想,三個戰(zhàn)略思想,還有三個核彈級思想。就是個日志文件功能,如果是你設(shè)計,能有什么想法?而往往是簡單中蘊含的偉大,才能更加讓人震撼。現(xiàn)在咱們就按圖索驥,開始一段與頂尖高手同行的代碼探險之旅。

這段代碼是個標(biāo)準(zhǔn)的C++類,為方便演示,我使用的是其Windows平臺的版本,此類可以在所有Visual Studio的C++應(yīng)用中使用,就兩個文件:LogFile.h和LogFile.cpp。

可以先總覽一下:

LogFile.h

// Log.h: interface for the CLog class.
//
//////////////////////////////////////////////////////////////////////


#if !defined(AFX_LOG_H__512AFEC0_D4E8_47F0_AB0C_4E29DAB9A9FC__INCLUDED_)
#define AFX_LOG_H__512AFEC0_D4E8_47F0_AB0C_4E29DAB9A9FC__INCLUDED_


#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


#define _CRT_SECURE_NO_WARNINGS


#include
#include
#include
#include


#define MAX_LENGTH_CONTENT_PER_LINE 1024


class CLogFile {
public:
CLogFile(LPCTSTR pszPathName4User = _T(""));
virtual ~CLogFile();
bool Record(LPCTSTR pszFormat, ...);


bool SetPathName4Host(LPCTSTR pszPathName4Host);
bool SetPathName4User(LPCTSTR pszPathName4User);
bool SetFileName4Host(LPCTSTR pszFileName4Host);
bool SetFileName4User(LPCTSTR szFileName4User);
bool SetHeader(LPCTSTR szHeader);


LPCTSTR GetPathName4Host();
LPCTSTR GetPathName4User();
LPCTSTR GetFileName4Host();
LPCTSTR GetFileName4User();


LPCTSTR GetPathName();
LPCTSTR GetFileNameFullPath();


protected:
SYSTEMTIME m_tSystemTime;


TCHAR m_szPathName4Host[MAX_PATH + 1];
TCHAR m_szPathName4User[MAX_PATH + 1];
TCHAR m_szFileName4Host[MAX_PATH + 1];
TCHAR m_szFileName4User[MAX_PATH + 1];


TCHAR m_szPathName[MAX_PATH + 1];
TCHAR m_szFileNameFullPath[MAX_PATH + 1];


TCHAR m_szHeader[MAX_LENGTH_CONTENT_PER_LINE + 1];
TCHAR m_szLine[MAX_LENGTH_CONTENT_PER_LINE + 1];


bool IsPathOrFileExist(LPCTSTR pszPathOrFileName);
bool BuildFilePath();
bool BuildPathAndFilePath();
};


#endif // !defined(AFX_LOG_H__512AFEC0_D4E8_47F0_AB0C_4E29DAB9A9FC__INCLUDED_)
LogFile.cpp
// Log.cpp: implementation of the CLog class.
//
//////////////////////////////////////////////////////////////////////


#include "LogFile.h"


#define ZERO_MEMORY(p) memset(p, 0, sizeof(p))


CLogFile::CLogFile(LPCTSTR pszPathName4User) {
ZERO_MEMORY(m_szPathName4Host);
ZERO_MEMORY(m_szPathName4User);
ZERO_MEMORY(m_szFileName4Host);
ZERO_MEMORY(m_szFileName4User);
ZERO_MEMORY(m_szPathName);
ZERO_MEMORY(m_szFileNameFullPath);
ZERO_MEMORY(m_szLine);
ZERO_MEMORY(m_szHeader);
_tcscpy_s(m_szHeader, MAX_LENGTH_CONTENT_PER_LINE, _T("F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15"));


::GetModuleFileName(NULL, m_szFileNameFullPath, MAX_PATH);
_tsplitpath(m_szFileNameFullPath, m_szPathName4Host, m_szPathName, m_szFileName4Host, NULL);
_tcscat_s(m_szPathName4Host, MAX_PATH, m_szPathName);


if (pszPathName4User) {
if (*pszPathName4User) {
_tcscpy_s(m_szPathName4User, MAX_PATH, pszPathName4User);
}
else {
*m_szPathName4User = _T('.');
_tcscat_s(m_szPathName4User, MAX_PATH, m_szFileName4Host);
}
}


BuildPathAndFilePath();
}


CLogFile::~CLogFile() {
}


bool CLogFile::BuildFilePath() {
GetLocalTime(&m_tSystemTime);
int iReturn = _sntprintf(m_szFileNameFullPath, MAX_PATH, _T("%s%s.%s.%04d%02d%02d.%p.txt"),
m_szPathName, m_szFileName4Host, m_szFileName4User,
m_tSystemTime.wYear, m_tSystemTime.wMonth, m_tSystemTime.wDay, this);


return 0 < iReturn;
}


bool CLogFile::BuildPathAndFilePath() {
bool bReturn = 0 < _sntprintf(m_szPathName, MAX_PATH, _T("%s%s"), m_szPathName4Host, m_szPathName4User);
if (bReturn) {
bReturn = BuildFilePath();
}
return bReturn;
}


bool CLogFile::Record(LPCTSTR pszFormat, ...) {
int iReturn = 0;
FILE* pFile = NULL;
do {
if (!IsPathOrFileExist(m_szPathName)) {
break;
}
if (!BuildFilePath()) {
break;
}
bool bIsNew = !IsPathOrFileExist(m_szFileNameFullPath);
pFile = _tfopen(m_szFileNameFullPath, _T("a"));
if (!pFile) {
break;
}
if (bIsNew) {
iReturn = _ftprintf(pFile, _T("Time User %s "), m_szHeader);
if (0 >= iReturn) {
break;
}
}
va_list vlArgs;
va_start(vlArgs, pszFormat);
iReturn = _vsntprintf(m_szLine, MAX_LENGTH_CONTENT_PER_LINE, pszFormat, vlArgs);
va_end(vlArgs);
if (0 > iReturn) {
break;
}
iReturn = _ftprintf(pFile, _T("%02d:%02d:%02d.%03d %s %s "), m_tSystemTime.wHour,
m_tSystemTime.wMinute, m_tSystemTime.wSecond, m_tSystemTime.wMilliseconds,
m_szFileName4User, m_szLine);
} while (false);
if (pFile) {
fclose(pFile);
}
return 0 < iReturn;
}


bool CLogFile::IsPathOrFileExist(LPCTSTR pszPathOrFileName) {
return (0 == _taccess(pszPathOrFileName, 0));
}


bool CLogFile::SetPathName4Host(LPCTSTR pszPathName4Host) {
_tcsncpy(m_szPathName4Host, pszPathName4Host, MAX_PATH);
return BuildPathAndFilePath();
}


bool CLogFile::SetPathName4User(LPCTSTR pszPathName4User) {
_tcsncpy(m_szPathName4User, pszPathName4User, MAX_PATH);
return BuildPathAndFilePath();
}


bool CLogFile::SetFileName4Host(LPCTSTR pszFileName4Host) {
_tcsncpy(m_szFileName4Host, pszFileName4Host, MAX_PATH);
return BuildFilePath();
}


bool CLogFile::SetFileName4User(LPCTSTR szFileName4User) {
_tcsncpy(m_szFileName4User, szFileName4User, MAX_PATH);
return BuildFilePath();
}


bool CLogFile::SetHeader(LPCTSTR szHeader) {
_tcsncpy(m_szHeader, szHeader, MAX_PATH);
return true;
}


LPCTSTR CLogFile::GetPathName4Host() {
return m_szPathName4Host;
}


LPCTSTR CLogFile::GetPathName4User() {
return m_szPathName4User;
}


LPCTSTR CLogFile::GetFileName4Host() {
return m_szFileName4Host;
}


LPCTSTR CLogFile::GetFileName4User() {
return m_szFileName4User;
}


LPCTSTR CLogFile::GetPathName() {
return m_szPathName;
}


LPCTSTR CLogFile::GetFileNameFullPath() {
return m_szFileNameFullPath;
}
戰(zhàn)術(shù)思想

先從戰(zhàn)術(shù)思想談起,有十一個,容我逐一道來。

戰(zhàn)術(shù)思想一:全名命名規(guī)則

代碼的第一眼感覺,沒有注釋!這幫高手果然很清高啊,他們不會幫助你看懂,因為這個世界上總有人不配看懂。只能沉下心來,自力更生了。再仔細(xì)看代碼,會發(fā)現(xiàn)代碼中的變量名、方法名都很長。第一個戰(zhàn)術(shù)級思想浮出水面:全名命名規(guī)則。命名使用單詞全名,而很多程序員喜歡使用縮寫,而縮寫并不一定能與所有人達(dá)成共識,導(dǎo)致命名的意義大打折扣。良好的命名可以代替注釋,且效率更高。微軟的函數(shù)命名平均長度是13個字母,而0day代碼中的命名平均長度是16.8個字母,超過微軟水準(zhǔn)將近30%。可以說,命名平均長度能夠作為代碼段位的參考之一。

戰(zhàn)術(shù)思想二:前綴命名規(guī)則

再仔細(xì)看每一個命名,第二個戰(zhàn)術(shù)級思想浮出水面:前綴命名規(guī)則。所有的變量名都有類型前綴,如字符串變量的前綴是“sz”,字符串指針變量的前綴是“psz” ,整型(int)變量的前綴是“i” ,布爾型(bool)變量的前綴是“b”,這些類型前綴雖然使用了縮寫,但這些縮寫都是C/C++程序員所共識的。還有,所有的類成員級變量在類型前綴前再加上“m_”前綴指示作用域,m是member的縮寫,其實還有一個“g_”前綴代表全局作用域,但全局變量只有在C代碼中很常見,而在C++代碼中幾乎從不使用。

戰(zhàn)術(shù)思想三:名詞前置命名規(guī)則

再再仔細(xì)看每一個命名,第三個戰(zhàn)術(shù)級思想浮出水面:名詞前置命名規(guī)則。例如類成員字符串變量“宿主路徑名”命名為m_szPathName4Host,“用戶路徑名”命名為m_szPathName4User,如果按人類正常思維應(yīng)該是m_szHostPathName和m_szUserPathName才對,但他們卻名詞前置,形容詞動詞后置。其目的是為了給相關(guān)命名進行分類,最早是為了能在代碼統(tǒng)計工具的報告中,能把相關(guān)命名在α排序中排在一起,以便進行代碼分析;而在后來的現(xiàn)代IDE的代碼編輯器中,都有自動完成功能,根據(jù)輸入的部分字母自動提示可能的輸入,按名詞前置命名規(guī)則,提示內(nèi)容將把相關(guān)命名排在一起,便于程序員選擇。如鍵入“m_szP”,將提示出m_szPathName4Host和m_szPathName4User,方便程序員在使用相關(guān)變量或方法時提高效率。

戰(zhàn)術(shù)思想四:介詞縮寫命名規(guī)則

在上面提到的命名中,都有一個阿拉伯?dāng)?shù)字4,這是什么鬼?第四個戰(zhàn)術(shù)級思想浮出水面:介詞縮寫命名規(guī)則。用4的英文諧音代替介詞“for”,原命名應(yīng)為m_szPathNameForHost,介詞作為前置命名分類與后置形容詞、動詞的分界線被大量使用,為節(jié)約鍵擊次數(shù)而在組織內(nèi)約定的縮寫。類似還有2,諧音英文的“to”,因為在程序中各種轉(zhuǎn)換也非常多,如BinToHex(二進制轉(zhuǎn)十六進制),可以縮寫為Bin2Hex。這可以理解為長命名思想與少鍵擊思想的辯證統(tǒng)一。

戰(zhàn)術(shù)思想五:對稱命名規(guī)則

看上面的成員變量和對應(yīng)的設(shè)置方法名和獲取方法名,第五個戰(zhàn)術(shù)級思想浮出水面:對稱命名規(guī)則。如此整齊劃一的命名,不但能幫助閱讀者在沒有注釋的情況下快速理解各方法的意圖,還能讓使用者無需翻看源碼就能準(zhǔn)確調(diào)用。

大家看看,一個小小的命名,已經(jīng)是殺機四伏,下足了功夫。十一個戰(zhàn)術(shù)思想接近一半,都是在談命名。就是因為命名是代碼的基石,它是多米諾骨牌效應(yīng)里的第一塊骨牌,每塊磚不做好,將會影響整個大廈的安危。這些命名規(guī)則的終極目標(biāo)都是為了用空間換時間。在你的每一次鍵擊中,每個思想可能只為你節(jié)約了0.1秒,但經(jīng)不住長年累月的積累,你的有效編程時間就是比別人多,還沒開始比賽,你就已經(jīng)勝過了。

戰(zhàn)術(shù)思想六:使用制表符縮進

代碼中還有一個不易察覺的細(xì)節(jié),其代碼縮進使用的是制表符(TAB鍵),第六個戰(zhàn)術(shù)級思想浮出水面:使用制表符縮進。關(guān)于縮進使用制表符還是空格,業(yè)界一直爭論不斷,且沒什么定論,主要原因就是覺得這是個小問題,無傷大雅,大家隨意,開心就好。但這些頂尖高手只用制表符,原因很暖心,僅僅是為了尊重同行!制表符最早出現(xiàn)是為了控制打印機在打印時的左邊距,當(dāng)時定義為8個空格,可視化編程出現(xiàn)后才用于代碼縮進,但當(dāng)時顯示器的分辨率是320*200,一行最多顯示80個字符,這8個空格實在是太長了,于是就在編輯器中定義為4個空格,但后來有人覺得2個才好,還有人覺得1個更好,最后干脆作為編輯器配置項,根據(jù)喜好自定義吧。所以使用制表符縮進的代碼在編輯器中的顯示樣式將會符合當(dāng)前使用者的習(xí)慣,而使用空格縮進的代碼將可能會導(dǎo)致當(dāng)前使用者不適。多么細(xì)致的人文關(guān)懷,面向人性編程,面向開發(fā)者編程,時刻謹(jǐn)記。

戰(zhàn)術(shù)思想七:調(diào)用必須有返回值

觀察代碼中的每一個方法,發(fā)現(xiàn)都有返回值,哪怕是返回固定值!

第七個戰(zhàn)術(shù)級思想浮出水面:調(diào)用必須有返回值。絕大多數(shù)編程語言都允許調(diào)用沒有返回值,但這幫頂級精英為什么在可以用這個規(guī)則的情況下還是不用呢?這就是接口的藝術(shù),為了向下兼容,未雨綢繆,面向未來編程!因為誰也無法預(yù)測,隨著代碼的不斷迭代,這個方法的使用條件可能會發(fā)生變化,而有返回值的調(diào)用是可以兼容沒有返回值的調(diào)用的,這樣可保持接口的歷史一致性,進退自如。這樣的設(shè)計一旦在public調(diào)用中發(fā)揮過一次作用,可就不是節(jié)約0.1秒的事了。

戰(zhàn)術(shù)思想八:減少嵌套深度

上面是Record方法中的一段代碼,使用了一個do-while循環(huán)語句,但循環(huán)條件是個固定布爾值false,意味著這個循環(huán)永遠(yuǎn)只會執(zhí)行一次,但為什么還要用循環(huán)語句呢?如果不用循環(huán)語句,正常的寫法應(yīng)該是這樣的:

第八個戰(zhàn)術(shù)級思想浮出水面:減少嵌套深度。嵌套深度決定了人類大腦的思考深度,而思考深度則決定了消耗的能量和思考的難度。所以嵌套深度較低的代碼,讓人思考起來會比較輕松且不易出錯,而重度嵌套的代碼則更容易讓人疲倦且增加產(chǎn)生bug的幾率。

戰(zhàn)術(shù)思想九:辯證使用goto

這段代碼使用do-while循環(huán)語句的結(jié)構(gòu),并配合break語句來減少邏輯嵌套。第九個戰(zhàn)術(shù)級思想浮出水面:辯證使用goto。break語句的本質(zhì)是goto語句,只是受限而已。而goto語句在早期面向過程編程的時代,由于其高效的操作效率而被濫用,把代碼寫的像面條一樣,扯不清,理還亂,這導(dǎo)致了上世紀(jì)60年代的軟件危機,并最終引發(fā)了軟件工程革命。在面向?qū)ο缶幊痰臅r代,業(yè)界統(tǒng)一的共識是禁止使用goto。但goto語句的操作效率確實很高,所以善用break這種閹割版goto可以起到魚與熊掌兼得的效果。

戰(zhàn)術(shù)思想十:同一函數(shù)代碼不要跨屏

觀察Record方法的代碼行數(shù)達(dá)到36行,但業(yè)界一般的說法是每個函數(shù)的代碼行數(shù)不要超過30行,理由是人類的腦容量問題。但0day的判斷標(biāo)準(zhǔn)是,第十個戰(zhàn)術(shù)級思想浮出水面:同一函數(shù)代碼不要跨屏。只要任意函數(shù)的所有代碼在當(dāng)前流行屏幕尺寸大小下能夠完全顯示即可。理由是只要整個代碼邏輯在人的靜態(tài)目視范圍之內(nèi),程序員的腦容量都夠用。除了靠減少代碼行數(shù)來防止縱向滾動屏幕,前面說的減少邏輯嵌套還能防止橫向滾動屏幕。代碼邏輯禁止跨屏規(guī)則能在很大程度上降低bug產(chǎn)生的幾率。

再觀察Record方法,發(fā)現(xiàn)一段有趣的代碼:

戰(zhàn)術(shù)思想十一:盡量使用順序代碼結(jié)構(gòu)代替判斷代碼結(jié)構(gòu)

BuildFilePath方法用于構(gòu)造日志文件名,其中使用了系統(tǒng)日期作為文件名的一部分,目的就是把日志文件按天分隔,以防止文件過大。這意味著每次寫日志,都應(yīng)判斷是否該更換文件名,但這種更換每天只發(fā)生一次。而這段代碼并沒有根據(jù)日期是否更改而構(gòu)造文件名,而是每次都按當(dāng)前日期構(gòu)造文件名,這意味著文件名在一天內(nèi)的調(diào)用中都是重復(fù)構(gòu)造相同的文件名,這不是做無用功嗎?第十一個戰(zhàn)術(shù)級思想浮出水面:盡量使用順序代碼結(jié)構(gòu)代替判斷代碼結(jié)構(gòu)。判斷語句是bug產(chǎn)生的源泉,盡量不要使用,哪怕代碼看上去有點愚蠢。不認(rèn)同的人可以試試,使用判斷語句來修改這段代碼,讓其看起來更有效率。當(dāng)你被源源不斷的bug改到懷疑人生時,你才能真切地體會到這個思想的精妙之處。對這個未知世界,心存敬畏,才能保你福如東海,壽比南山。

前面談到了十一個戰(zhàn)術(shù)思想,每一個戰(zhàn)術(shù)思想,可能看過來都不復(fù)雜,但偉大往往都藏在細(xì)節(jié)中。十一個戰(zhàn)術(shù)思想,處處都在體現(xiàn)著:以空間換時間,面向人性編程,面向開發(fā)者編程,面向未來編程,以及對這個未知世界心存敬畏。接下來我們談?wù)剳?zhàn)略級思想。

戰(zhàn)略級思想

戰(zhàn)略級思想一:贈人玫瑰,手有余香

再觀察Record方法的定義,使用了非常罕見的不定長參數(shù),第一個戰(zhàn)略級思想橫空出世:贈人玫瑰,手有余香。作為一個日志文件類的主要方法,通常就是把傳入的字符串參數(shù),存儲到日志文件里就好了。為什么要使用一個非常冷門的技術(shù)?原因就是尊重傳統(tǒng),方便你的同行,讓調(diào)用者更干、更爽、更安心。如果參數(shù)是一個字符串,則意味著調(diào)用方必須在調(diào)用此方法前,拼裝好字符串:

使用不定長參數(shù),則可以這樣調(diào)用:

一行搞定!把方便留給別人,把困難留給自己,雷鋒精神時刻謹(jǐn)記。面向人性編程,面向開發(fā)者編程,面向開源編程。

戰(zhàn)略級思想二:簡單通用

通覽整體代碼,系統(tǒng)調(diào)用只使用過一次Windows API,其余均使用C運行時庫函數(shù),第二個戰(zhàn)略級思想橫空出世:簡單通用。作為一個工具類,會被廣泛使用,包括跨平臺應(yīng)用。如果使用Windows API,此類要移植到Unix/Linux平臺上將付出巨大代價。而C運行時庫函數(shù)是語言標(biāo)準(zhǔn)而非平臺標(biāo)準(zhǔn),在功能表現(xiàn)上所有平臺都是一致的,所以移植成本要低的多。而且代碼中還使用了C運行時庫函數(shù)的自適應(yīng)字符集宏定義版本,使得此工具類無論編譯目標(biāo)應(yīng)用是MBCS字符集還是Unicode字符集都無需修改一行代碼!事實上,此工具類在組織內(nèi)不但有多平臺版本,甚至還有多語言版本,包括C#、java、VB等。受益于使用語言標(biāo)準(zhǔn)的設(shè)計思想,各語言、平臺版本的代碼一致性很高,產(chǎn)生bug的幾率很小,移植成本非常低。

現(xiàn)在我們正式開始通過瀏覽代碼來理解代碼邏輯,先看類構(gòu)造函數(shù):

戰(zhàn)略級思想三:默認(rèn)值的藝術(shù)

這個初始化還是相當(dāng)復(fù)雜的,對關(guān)鍵類成員變量的默認(rèn)值進行了規(guī)劃和設(shè)計,第三個戰(zhàn)略級思想橫空出世:默認(rèn)值的藝術(shù)。為便于理解程序的設(shè)計思想,我寫了一個測試程序,使用不同的參數(shù)調(diào)用構(gòu)造方法,然后調(diào)用對應(yīng)的成員變量獲取方法,以查看成員變量的內(nèi)容:

從以上結(jié)果可以看出,構(gòu)造函數(shù)通過傳遞不同的參數(shù),將成員變量初始化為不同使用理念的數(shù)據(jù)套。目的就是讓調(diào)用者在構(gòu)造完類后,即可使用Record方法開始記錄日志,而無需任何配置!還是那句老話:把方便留給別人,把麻煩留給自己,雷鋒精神時刻謹(jǐn)記。

三個戰(zhàn)略級思想,以小搏大,已經(jīng)從簡單到人性上升到墨家的兼愛和利他主義,這其實就是面向開源的編程思想內(nèi)核。下面我再談三個核彈級思想。

核彈級思想

核彈級思想一:即時熱調(diào)試

繼續(xù)仔細(xì)研讀測試結(jié)果,可了解代碼初始化意圖:給構(gòu)造函數(shù)傳參空指針(NULL),則日志文件路徑自動配置為當(dāng)前可執(zhí)行文件路徑,緊接著調(diào)用Record方法即可產(chǎn)生日志文件,nice!如果給構(gòu)造函數(shù)傳參非空字符串,如示例中是“l(fā)og”,則自動配置日志文件路徑為當(dāng)前可執(zhí)行文件路徑后再附加“l(fā)og”路徑,enn…如果傳參是空字符串或不傳任何參數(shù)(這是默認(rèn)情況,應(yīng)該是該類建議的主要使用方式),則自動配置日志文件路徑為當(dāng)前可執(zhí)行文件路徑后再附加帶前綴“.”的不包括擴展名的可執(zhí)行文件名,what?

代碼是看懂了,但為啥?難道要自動創(chuàng)建如此詭異的路徑?但在Record方法中,不但沒有找到創(chuàng)建路徑的方法,還看到了這樣一段代碼:

這段代碼的意思就是當(dāng)日志文件路徑不存在時,將退出Record功能,什么也不干!這個類的作用不就是記錄日志嗎?居然在某些情況下還不應(yīng)記錄?What the fuck!

第一顆核彈君臨天下:即時熱調(diào)試。像C/C++這種接近硬件底層的編譯型語言,預(yù)定義有兩種編譯應(yīng)用的形態(tài):debug版本和release版本。debug版本用于在開發(fā)環(huán)境中調(diào)試,尤其是單步調(diào)試功能可以解決硬核的技術(shù)問題,而release版本用于正式發(fā)布,沒有調(diào)試功能。但代碼調(diào)試時,除了技術(shù)問題,還有更大量的業(yè)務(wù)邏輯問題需要調(diào)試。如果使用單步調(diào)試效率太低了,所以絕大多數(shù)C/C++程序員在debug版本中通過輸出日志調(diào)試業(yè)務(wù)邏輯。這些日志通過宏定義控制只在debug版本中編譯,而在release版本中忽略,因為正式發(fā)布的軟件不能在用戶方產(chǎn)生大量調(diào)試日志,否則日積月累會塞滿用戶的存儲空間。但是,誰也不能保證在debug版本中能調(diào)試完所有的業(yè)務(wù)邏輯問題,如果在用戶方部署的release版本出錯,大家束手無策。在互聯(lián)網(wǎng)發(fā)明以前,這個問題到也不太重要,因為即使在用戶方發(fā)現(xiàn)程序錯誤,程序員也沒辦法到達(dá)現(xiàn)場解決問題。但現(xiàn)在的互聯(lián)網(wǎng)技術(shù)可以支撐遠(yuǎn)程登錄服務(wù)器或者個人計算機,賦予了技術(shù)支持人員可以在任何時間、任何地點、使用任何設(shè)備到達(dá)錯誤現(xiàn)場的能力,但老舊的編譯時debug和release機制,在新時代下也沒什么卵用。0day的精英們與時俱進,設(shè)計了這個動態(tài)debug和release機制:如果在當(dāng)前可執(zhí)行文件的目錄下,存在一個特別指定的目錄,則程序進入debug狀態(tài),并在那個目錄下生成日志;否則程序保持release狀態(tài),不輸出日志。牛B的思想閃耀星空!把代碼的debug和release狀態(tài)確認(rèn)由編譯時后移到運行時,這意味著當(dāng)程序發(fā)生業(yè)務(wù)邏輯問題,程序員可直接登錄到現(xiàn)場,程序都不用重啟,直接建立指定目錄,即可知道當(dāng)前程序正在干什么,找到問題后,再把目錄一刪,揮一揮衣袖,不帶走一片云彩!

這個頂級設(shè)計還有一些非常貼心的細(xì)節(jié)設(shè)計,第一是關(guān)于那個指定的目錄。默認(rèn)是不包括擴展名的當(dāng)前可執(zhí)行文件名,前面還有一個“.”。這是為了保持跨平臺操作的一致性,因為Unix/Linux平臺下的可執(zhí)行文件沒有擴展名,如果單純使用當(dāng)前可執(zhí)行文件名,則因為重名而無法創(chuàng)建目錄,所以前面加個“.”來保證不重名,還順便成為隱藏目錄,因為Unix/Linux的文件系統(tǒng)定義以“.”開頭的目錄或文件具備隱藏屬性。雖然Windows平臺下不存在這些問題,但0day的絕大多數(shù)精英都是Windows平臺和Unix/Linux平臺雙料王牌,經(jīng)常需要在多平臺間切換工作,為保持操作一致性,只好委屈一下Windows平臺了。但也無需焦慮,這個指定目錄可以在初始化或運行時隨便修改。修改指定目錄還有一個使用技巧,比如在同一目錄下有A1,A2,A3,B1,B2共5個應(yīng)用程序,其中A1,A2,A3是有鉤稽關(guān)系的第一組應(yīng)用,B1,B2是有鉤稽關(guān)系的第二組應(yīng)用,可以設(shè)計為建立A目錄,則在A目錄中同時產(chǎn)生A1,A2,A3的日志,建立B目錄,則在B目錄中同時產(chǎn)生B1,B2的日志,達(dá)到相關(guān)應(yīng)用群日志自動分組的目的。

第二是關(guān)于日志文件名。整個文件名分為5個部分:第一部分是應(yīng)用程序名,這個很容易理解,一看就知道這個日志是哪個應(yīng)用產(chǎn)生的;第二部分是一個自定義的名字,這個作用比較硬核,咱門后面再講;第三部分是日志產(chǎn)生的日期,為了防止文件過大,每個應(yīng)用程序每天只有一個日志文件;第四部分比較特殊,是運行時日志文件類實例的內(nèi)存地址,what?這能干啥用?使用實例的內(nèi)存地址意味著每次啟動這個類,文件名就會發(fā)生變化,可用于指示這個應(yīng)用程序在這個日期下的不同啟動批次。第五部分是固定擴展名“txt”,指示系統(tǒng)可用文本編輯器打開此文件。整個設(shè)計考慮了使用上的方方面面,盡量讓使用者更方便、更舒適,愛心媽媽,呵護全家。面向人性編程,面向開發(fā)者編程,面向開源編程。

核彈級思想二:統(tǒng)計日志

再看向文件寫入內(nèi)容的代碼中使用制表符“ ”作為輸出內(nèi)容的分隔符,第二顆核彈石破天驚:統(tǒng)計日志。為了能理解這個設(shè)計,咱們先看看調(diào)用方是如何使用這個類的,典型調(diào)用像這樣:

意圖就是把需要輸出的狀態(tài)、數(shù)據(jù),如調(diào)用的方法名、錯誤描述等,組織成類似表格字段的方式分隔輸出。使用制表符可以保證用表格軟件打開日志文件或把文本復(fù)制到表格軟件里,效果是這樣的:

在第一個核彈的淫威下,再加上日志的記錄時間精確到毫秒的加持,程序員們徹底放開了,寫日志跟不要錢似的,瘋狂輸出,幾乎每個函數(shù)在返回前都會把當(dāng)前處理結(jié)果輸出到日志里,面對這樣的海量日志,用眼睛找bug會看瞎的。所以創(chuàng)造性的利用表格的相關(guān)排序、分類匯總、透視圖等統(tǒng)計功能,快準(zhǔn)狠地定位查找目標(biāo)。比如示例那個日志,用透視圖看是這樣的:

或者是這樣的:

就說你想查啥?咋樣都行,就是拖拖拽拽的事。bug往哪里躲?它太難了…羽扇綸巾,談笑間,強擼灰飛煙滅。

核彈級思想三:調(diào)試多線程

還記得前面講到文件名的第二部分嗎?就是代碼里的成員變量m_szFileName4User,它是干什么用的?看遍代碼的上上下下,也看不出個所以然。我們對其賦值“robot”,看看出現(xiàn)啥情況:

日志文件名第二部分變成“robot”,日志文件中user列里面填充“robot”,仍然一頭霧水!第三顆潛射核彈韜跡隱智:調(diào)試多線程。多線程調(diào)試是程序員的噩夢,因為人類的大腦無法精確模擬計算機多線程的運行過程。所以多線程程序所產(chǎn)生的bug,尤其是無法必現(xiàn)的bug,常常讓人束手無策。在前面兩顆核彈的加持下,給解決這個問題帶來了希望。如果在日志文件類中加入同步機制,多個線程共享同一個日志文件類實例,則會導(dǎo)致多線程程序在調(diào)試狀態(tài)下被強制串行化為單線程程序,由于運行環(huán)境的變化很可能觸發(fā)不了那個多線程bug,所以每個線程必須單獨使用各自的日志文件。在分析時,將相關(guān)的所有線程日志全部拷貝到一個表格文件中,利用排序功能就能知道每個時刻,各個線程都正在干什么。這個user列就是用于區(qū)分這條記錄來自于哪個線程。多線程調(diào)試就這么被輕松地搞定了!說了那么多偉大,我都厭倦了:“老婆,快出來看上帝?!?/p>

核彈級思想是高手隱藏在編程中的不容易直接悟出來的彩蛋。但你一旦悟出來,一定會有醍醐灌頂?shù)臅晨炝芾臁?br>

寫在最后

我已把這段代碼上傳到gitee,訪問地址https://gitee.com/aeye/CTools/,歡迎大家共建,也可應(yīng)用于項目中,給大家在代碼的海洋中探險時提供一把趁手的兵器。

最后,希望同學(xué)們也能夠創(chuàng)造出有思想,有靈魂,舉手投足之間都透露出優(yōu)雅的代碼:


<本文完>





原文標(biāo)題:河套IT TALK——TALK 12:編程的技術(shù)|藝術(shù)|術(shù)術(shù) 下篇:對著代碼解讀編程的哲學(xué)

文章出處:【微信公眾號:開源技術(shù)服務(wù)中心】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 開源技術(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    389

    瀏覽量

    7891
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3611

    瀏覽量

    15959

原文標(biāo)題:河套IT TALK——TALK 12:編程的技術(shù)|藝術(shù)|術(shù)術(shù) 下篇:對著代碼解讀編程的哲學(xué)

文章出處:【微信號:開源技術(shù)服務(wù)中心,微信公眾號:共熵服務(wù)中心】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    工業(yè)機器人的四種編程(示教編程、離線編程、自增強現(xiàn)實編程編程)剖析!

    和工作量,提高編程效率,實現(xiàn)編程的自適應(yīng)性,從而提高生產(chǎn)效率,是機器人編程技術(shù)發(fā)展的終極追求。本文將就機器人編程技術(shù)的發(fā)展作一介紹,希望能給讀者帶來一些啟發(fā)。對工業(yè)
    的頭像 發(fā)表于 08-30 12:14 ?929次閱讀
    工業(yè)機器人的四種<b class='flag-5'>編程</b>(示教<b class='flag-5'>編程</b>、離線<b class='flag-5'>編程</b>、自增強現(xiàn)實<b class='flag-5'>編程</b>主<b class='flag-5'>編程</b>)剖析!

    解讀 MEMS 可編程 LVCMOS 振蕩器 SiT1602 系列:精準(zhǔn)頻率的創(chuàng)新之選

    解讀 MEMS 可編程 LVCMOS 振蕩器 SiT1602 系列:精準(zhǔn)頻率的創(chuàng)新之選
    的頭像 發(fā)表于 08-09 15:39 ?216次閱讀
    <b class='flag-5'>解讀</b> MEMS 可<b class='flag-5'>編程</b> LVCMOS 振蕩器 SiT1602 系列:精準(zhǔn)頻率的創(chuàng)新之選

    解讀 MEMS 可編程 LVCMOS 振蕩器 SiT8008 系列:精準(zhǔn)與靈活的時脈之選

    解讀 MEMS 可編程 LVCMOS 振蕩器 SiT8008 系列:精準(zhǔn)與靈活的時脈之選
    的頭像 發(fā)表于 08-09 10:29 ?221次閱讀
    <b class='flag-5'>解讀</b> MEMS 可<b class='flag-5'>編程</b> LVCMOS 振蕩器 SiT8008 系列:精準(zhǔn)與靈活的時脈之選

    廈門市開源芯片產(chǎn)業(yè)促進會R-Talk第10期成功舉辦

    2024年6月27日,由中國開放指令生態(tài)(RISC-V)聯(lián)盟福建區(qū)域中心和廈門市開源芯片產(chǎn)業(yè)促進會(“開芯會”)聯(lián)合主辦,廈門市必易微電子科技有限公司協(xié)辦的R-Talk第10期活動在必易微公司成功
    的頭像 發(fā)表于 06-29 08:37 ?311次閱讀
    廈門市開源芯片產(chǎn)業(yè)促進會R-<b class='flag-5'>Talk</b>第10期成功舉辦

    PLC的編程方式及編程語言

    在工業(yè)自動化領(lǐng)域,PLC(Programmable Logic Controller,可編程邏輯控制器)因其強大的控制功能和靈活的編程方式而得到了廣泛應(yīng)用。PLC的編程方式和編程語言是
    的頭像 發(fā)表于 06-27 14:08 ?492次閱讀

    編程電源如何編程

    編程電源如何編程? 可編程電源是一種可以調(diào)節(jié)輸出電壓和電流的電源設(shè)備,廣泛應(yīng)用于電子設(shè)備測試、研發(fā)和生產(chǎn)等領(lǐng)域。通過編程,用戶可以根據(jù)需要設(shè)置電源的輸出參數(shù),實現(xiàn)自動化測試和控制。本
    的頭像 發(fā)表于 06-10 15:24 ?1021次閱讀

    流式細(xì)胞術(shù): OEM 激光引擎帶來諸多優(yōu)勢

    ? 基于微型元件的永久對準(zhǔn)激光引擎降低了成本,簡化了組裝,可以為新一代流式細(xì)胞儀提供更好的變異系數(shù)。要想在任何地方使用激光,流式細(xì)胞術(shù),特別是多參數(shù)流式細(xì)胞術(shù)是具挑戰(zhàn)性的儀器應(yīng)用之一。 這是因為多個
    的頭像 發(fā)表于 06-03 06:26 ?1091次閱讀

    奇異摩爾攜手SEMiBAY Talk 邀您暢談互聯(lián)與計算

    2024年5月25日(本周六)19:30,由深圳市半導(dǎo)體與集成電路產(chǎn)業(yè)聯(lián)盟(SICA)主辦的 SEMiBAY Talk“Chiplet 與先進封裝技術(shù)和市場趨勢”將在線上舉行。奇異摩爾產(chǎn)品及解決方案
    的頭像 發(fā)表于 05-20 18:31 ?898次閱讀
    奇異摩爾攜手SEMiBAY <b class='flag-5'>Talk</b> 邀您暢談互聯(lián)與計算

    傳智教育聯(lián)合科大訊飛舉辦“AI開發(fā)者TALK”活動

    3月23日,由傳智教育與科大訊飛聯(lián)合組織的大模型實戰(zhàn)應(yīng)用之“AI開發(fā)者 TALK·北京站”在海淀舉辦。本次活動圍繞“大模型應(yīng)用”展開探討,旨在為廣大AI開發(fā)者提供一個交流、學(xué)習(xí)和展示的平臺。 活動
    的頭像 發(fā)表于 03-26 16:12 ?372次閱讀
    傳智教育聯(lián)合科大訊飛舉辦“AI開發(fā)者<b class='flag-5'>TALK</b>”活動

    用于流式細(xì)胞術(shù)的新型紫外激光器

    現(xiàn)在,相干公司的 OBIS XT “智能”緊湊型紫外激光器系列,具有高達(dá) 150 mW 功率,全新 320 nm 波長。 在多參數(shù)流式細(xì)胞術(shù)中,儀器和熒光調(diào)色板目前都進一步推向紫外波段,以增加參數(shù)
    的頭像 發(fā)表于 03-26 06:41 ?240次閱讀

    g73編程R怎么算

    編程是一門使用計算機語言來創(chuàng)建、編寫和修改代碼的技能。在編程過程中,計算機程序員通過使用各種編程語言來告訴計算機執(zhí)行特定的任務(wù)。其中,G73編程
    的頭像 發(fā)表于 02-14 15:57 ?640次閱讀

    在數(shù)控編程中,g代碼的作用是什么

    在數(shù)控編程中,G代碼是一種用于控制數(shù)控機床運動和功能的編程語言。它是數(shù)控加工過程中的重要組成部分,通過編寫G代碼,人們可以靈活地控制數(shù)控機床執(zhí)行各種精密和復(fù)雜的操作,從而實現(xiàn)高精度、高
    的頭像 發(fā)表于 02-14 15:53 ?1178次閱讀

    數(shù)控編程的g功能代碼是什么

    數(shù)控編程中,G代碼(也稱為指令代碼)是一種用于控制數(shù)控機床運動、輔助功能和工作過程的指令。在數(shù)控編程中,通過一系列的G代碼指令的組合和排列,
    的頭像 發(fā)表于 02-14 15:51 ?3212次閱讀

    Tech Talk解讀閃存原理與顆粒類型

    ,NAND閃存的存儲方式和堆疊技術(shù)也在持續(xù)演進。本文將圍繞閃存顆粒相關(guān)的概念以及發(fā)展趨勢做介紹。NAND閃存單元NAND閃存基于浮柵晶體管,通過其中所存儲的電荷量表示不同
    的頭像 發(fā)表于 02-05 18:01 ?853次閱讀
    Tech <b class='flag-5'>Talk</b>:<b class='flag-5'>解讀</b>閃存原理與顆粒類型

    編程語言中一個奇怪的代碼結(jié)構(gòu)

    在C語言和C++等編程語言中,我們常常會遇到一個奇怪的代碼結(jié)構(gòu)。
    發(fā)表于 11-01 10:24 ?352次閱讀
    <b class='flag-5'>編程</b>語言中一個奇怪的<b class='flag-5'>代碼</b>結(jié)構(gòu)