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

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

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

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

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



往期回顧

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

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

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

一段黑客的代碼

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

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

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

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

這段代碼是個(gè)標(biāo)準(zhǔn)的C++類,為方便演示,我使用的是其Windows平臺(tái)的版本,此類可以在所有Visual Studio的C++應(yīng)用中使用,就兩個(gè)文件: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ù)思想談起,有十一個(gè),容我逐一道來(lái)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

戰(zhàn)略級(jí)思想

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

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

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

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

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

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

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

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

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

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

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

核彈級(jí)思想

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

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

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

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

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

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

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

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

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

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

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

或者是這樣的:

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

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

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

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

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

寫在最后

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

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


<本文完>





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

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

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

    關(guān)注

    0

    文章

    389

    瀏覽量

    7942
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3723

    瀏覽量

    16340

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

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

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    光學(xué)全息術(shù)的原理及應(yīng)用領(lǐng)域

    本文介紹了光學(xué)全息術(shù)的原理及應(yīng)用領(lǐng)域。 當(dāng)我們第一次見到全息影像時(shí),仿佛親身踏入了科幻電影的奇幻世界,三維圖像懸浮在空氣中,無(wú)需任何特殊設(shè)備,我們就能從各個(gè)角度盡情欣賞。這種仿佛魔法般的成像技術(shù)
    的頭像 發(fā)表于 11-21 10:18 ?316次閱讀
    光學(xué)全息<b class='flag-5'>術(shù)</b>的原理及應(yīng)用領(lǐng)域

    廈門市開源芯片產(chǎn)業(yè)促進(jìn)會(huì)R-Talk12期成功舉辦

    2024年11月8日,由中國(guó)開放指令生態(tài)(RISC-V)聯(lián)盟福建區(qū)域中心和廈門市開源芯片產(chǎn)業(yè)促進(jìn)會(huì)(“開芯會(huì)”)聯(lián)合主辦的R-Talk12期活動(dòng)在中科(廈門)數(shù)據(jù)智能研究院成功舉辦。本次活動(dòng)邀請(qǐng)
    的頭像 發(fā)表于 11-09 01:10 ?324次閱讀
    廈門市開源芯片產(chǎn)業(yè)促進(jìn)會(huì)R-<b class='flag-5'>Talk</b>第<b class='flag-5'>12</b>期成功舉辦

    凱茉銳電子 術(shù)野攝像機(jī)與FCB-EV7520模組:點(diǎn)燃高清影像捕捉新火花

    在高清影像捕捉技術(shù)的不斷探索與追求中,術(shù)野攝像機(jī)與索尼FCB-EV7520模組的結(jié)合,無(wú)疑為這一領(lǐng)域注入了新的活力與可能。
    的頭像 發(fā)表于 11-04 17:44 ?186次閱讀

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

    和工作量,提高編程效率,實(shí)現(xiàn)編程的自適應(yīng)性,從而提高生產(chǎn)效率,是機(jī)器人編程技術(shù)發(fā)展的終極追求。本文將就機(jī)器人編程技術(shù)的發(fā)展作一介紹,希望能給讀者帶來(lái)一些啟發(fā)。對(duì)工業(yè)
    的頭像 發(fā)表于 08-30 12:14 ?2610次閱讀
    工業(yè)機(jī)器人的四種<b class='flag-5'>編程</b>(示教<b class='flag-5'>編程</b>、離線<b class='flag-5'>編程</b>、自增強(qiáng)現(xiàn)實(shí)<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 ?373次閱讀
    <b class='flag-5'>解讀</b> MEMS 可<b class='flag-5'>編程</b> LVCMOS 振蕩器 SiT1602 系列:精準(zhǔn)頻率的創(chuàng)新之選

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

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

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

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

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

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

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

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

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

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

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

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

    g73編程R怎么算

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

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

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

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

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

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

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