為什么要全面思考問題
????????□ 在軟件開發(fā)中,對一個問題思考得越全面,編寫出的代碼就會越嚴謹,出現(xiàn)bug的幾率就越低;反之,如果沒有對一個問題進行全面而深入的思考,編寫出的代碼就會漏洞百出,出現(xiàn)各種莫名其妙、無法復(fù)現(xiàn)的bug的幾率也就急劇增加。
????????□ 軟件就是數(shù)據(jù)加邏輯,數(shù)據(jù)是“肉身”,邏輯是“靈魂”。如果不全面思考問題,在某些情況下, “靈魂”就會“精神錯亂”,甚至損壞“肉身”,進而導(dǎo)致無法正常工作。
????????□ 只有經(jīng)過全面思考編寫出的代碼,才是嚴謹?shù)模拍鼙WC可靠性。一份代碼即使嚴格遵守了代碼規(guī)范,重構(gòu)了設(shè)計模式,但思考不全面,邏輯不嚴謹,也不能稱之為優(yōu)雅。
????????□ 沒有經(jīng)過全面思考開發(fā)出的軟件,雖然短期內(nèi)可能能正常工作,但長遠來看,各種問題和漏洞一定會爆發(fā)出來,從而導(dǎo)致系統(tǒng)的可靠性、可維護性和穩(wěn)定性大打折扣。記住墨菲定律:凡是你認為可能會出錯的,它一定會出錯。
????????下面,我們通過幾個實例來理解如何進行全面思考。
實例1
????????輸入若干個整數(shù)作為數(shù)組,將數(shù)組中每一個元素除以第一個元素的結(jié)果,作為新的數(shù)組元素值。
????????這道編程題并不難,稍加一思索,很容易給出下面的答案。
#include <iostream>
using namespace std;
static void DivArray(int *pnArray, int nSize)
{
for (int i = 0; i < nSize; i++)
{
pnArray[i] /= pnArray[0];
}
}
int main()
{
int nSize = 0;
cin >> nSize;
int *pnNumber = new int[nSize];
for (int i = 0; i < nSize; i++)
{
cin >> (*pnNumber++);
}
DivArray(pnNumber, nSize);
delete pnNumber;
pnNumber = NULL;
return 0;
}
????????看著是不是也沒什么大問題?實際上,這段代碼至少有以下6個問題:
????????1、沒有對輸入的nSize進行檢查,nSize可能為負數(shù)、0或者很大的一個正數(shù)(可能會導(dǎo)致內(nèi)存溢出)。
????????2、從cin輸入數(shù)組元素后,pnNumber已經(jīng)不指向第一個數(shù)組元素了,后面使用和釋放會出錯。
????????3、函數(shù)DivArray中,沒有對傳入的兩個參數(shù)進行檢查。
????????4、函數(shù)DivArray中,數(shù)組中第一個元素可能為0,被0除會導(dǎo)致程序崩潰。
????????5、函數(shù)DivArray中,從0開始遍歷數(shù)組,導(dǎo)致數(shù)組中第一個元素已經(jīng)變成了1,后面元素的邏輯均不正確。
????????6、釋放pnNumber時,應(yīng)當(dāng)使用delete [],而不是delete。
????????從這個例子可以看出來,問題簡單,并不代表不需要全面思考。不全面思考,后果很嚴重,你的代碼中到處都充斥著漏洞和bug。
實例2????????
????????自行封裝一個函數(shù),用于實現(xiàn)內(nèi)存拷貝,函數(shù)原型如下:
????????void *memcpy(void *dest, const void *src, size_t count);
????????有的新手看到這個題目,覺得so easy,立馬寫出了下面的代碼。
void *memcpy(void *dest, const void *src, size_t count)
{
if (src == NULL || dest == NULL)
{
return NULL;
}
while (count--)
{
*dest++ = *src++;
}
return dest;
}
????????很可惜,上面的代碼除了一些低級的語法錯誤外,思考得也不夠全面。
????????1、src和dest都是void *類型,不能對其進行++操作。
????????2、返回的dest指針,已經(jīng)不是原始傳入的dest指針了。
????????3、當(dāng)dest指向的內(nèi)存區(qū)域,與src指向的內(nèi)存區(qū)域有重疊時,可能導(dǎo)致拷貝錯誤的數(shù)據(jù)。
????????正確的實現(xiàn)可以參考下面的代碼。
void *memcpy(void *dest, const void *src, size_t count)
{
if (src == NULL || dest == NULL)
{
return NULL;
}
if (dest > src && (char *)dest < (char *)src + count)
{
char *pSrc = (char *)src + count - 1;
char *pDest = (char *)dest + count - 1;
while (count--)
{
*pDest-- = *pSrc--;
}
}
else
{
char *pSrc = (char *)src;
char *pDest = (char *)dest;
while (count--)
{
*pDest++ = *pSrc++;
}
}
return dest;
}
????????實際上,如果更進一步思考,上面的代碼還有優(yōu)化的空間。
????????1、當(dāng)count為0時,其實可以直接返回dest,后面的邏輯就不用考慮count了,更簡潔。
????????2、當(dāng)src和dest相等時,說明它們指向的是同一塊區(qū)域,可以直接返回dest,不需要再去拷貝。
????????3、當(dāng)count較大時,一個字節(jié)一個字節(jié)拷貝的方式,效率非常低。如果追求效率的話,需要考慮字節(jié)對齊和多字節(jié)拷貝等,可參考glibc中memcpy的實現(xiàn)(不支持內(nèi)存重疊)。
實例3
????????有一根長為L的平行于x軸的細木桿,其左端點的x坐標(biāo)為0(故右端點的x坐標(biāo)為L)。剛開始時,上面有N只螞蟻,第i(1≤i≤N)只螞蟻的橫坐標(biāo)為xi(假設(shè)xi已經(jīng)按照遞增順序排列),方向為di(0表示向左,1表示向右)。每只螞蟻都以速度v向前走,當(dāng)任意兩只螞蟻碰頭時,它們會同時調(diào)頭朝相反方向走,速度不變。編寫程序求解以下問題:
????????1、所有螞蟻都離開木桿需要多長時間?
????????2、所有螞蟻都離開木桿共碰撞了多少次?
????????3、第i只螞蟻離開木桿需要多長時間?
????????以下為這道題的思考過程。
????????1、因為每只螞蟻的初始方向是任意的,兩只螞蟻碰頭后會調(diào)頭,因此,所有螞蟻的實時坐標(biāo)是動態(tài)的,且是相互影響的,直接編程很難處理??紤]下面兩只螞蟻的情況,從碰頭到離開木板的時間,是x和10-x的較大值。
????????如果兩只螞蟻碰頭后,不調(diào)頭,而是仍按原方向前進,從碰頭到離開木板的時間,也是x和10-x的較大值,只不過后離開木板的那只螞蟻變了。也就是說,螞蟻碰頭后不調(diào)頭,不影響所有螞蟻離開木板的時間。
????????2、同上面的分析,螞蟻碰頭后不調(diào)頭,不影響螞蟻碰撞的次數(shù),只影響哪兩只螞蟻碰撞了。在此假設(shè)下,每只螞蟻碰撞的次數(shù),就是初始時,與其前進方向相反的螞蟻的個數(shù)。
????????3、與問題1和2不同,這里求解的是某只螞蟻離開木桿的時間,需要我們更深入地分析和思考問題。不管某只螞蟻中間和其他螞蟻碰撞了多少次,到最后一次碰撞時,這兩只螞蟻爬行的時間和路程是相同的,假設(shè)路程用A表示。碰撞后,各自調(diào)頭,離開目標(biāo),則向左離開木板的那只螞蟻爬行的總路程為:A+x。而這個A+x也可以理解為另一只螞蟻之前爬行的路程加上繼續(xù)向左離開木板爬行的路程。
????????怎么更形象地理解呢?可以假設(shè)每只螞蟻背著一粒米,碰頭時,交換各自的米,然后調(diào)頭。這樣,螞蟻雖然調(diào)頭了,但米的前進方向始終不變。螞蟻爬行的路程,就是它離開木板時背著的米走過的路程。那么,兩者如何關(guān)聯(lián)起來呢?
????????假設(shè)初始時,有P只螞蟻向左,則有N-P只螞蟻向右。因為每次碰撞后,向左和向右的螞蟻數(shù)量不變,因此,最終肯定有P只螞蟻從左邊離開木板,有N-P只螞蟻從右邊離開木板。是哪P只螞蟻呢?
????????假如螞蟻a初始在螞蟻b左邊,則根據(jù)規(guī)則,任何時候,螞蟻a的相對位置都在螞蟻b左邊。故初始時靠左的前P只螞蟻,必定會從左邊離開木板,其他N-P只螞蟻,必定會從右邊離開木板。
????????假如i不大于P,則第i只螞蟻會從左邊離開木板,它爬行的路程,就是它離開木板時背著的米走過的路程。它背著的米哪來的?來自初始時第i只向左的螞蟻。故初始時第i只向左的螞蟻的坐標(biāo),就是第i只螞蟻爬行的路程。同理,假如i大于P,則L減去第i-P只向右的螞蟻的坐標(biāo),就是第i只螞蟻爬行的路程。
實例4
????????嵌入式設(shè)備進行升級時,支持升級uboot、內(nèi)核、根文件系統(tǒng)和程序。以升級程序為例,我們在升級時的大致流程是什么樣的?有哪些需要注意的地方?
????????我們可以用下面的流程圖來理解嵌入式設(shè)備升級程序的處理邏輯。
? ? ? ? ??
????????1、返回進度時,應(yīng)包括接收數(shù)據(jù)的進度和向分區(qū)寫入數(shù)據(jù)的進度兩部分,比如:前者占60%,后者占40%,然后根據(jù)這個比例計算總的進度。
????????2、程序包應(yīng)當(dāng)將一些相關(guān)信息也打包進去,以便于校驗,比如:包的類型、版本號、對應(yīng)的硬件型號。包的類型用于區(qū)分是uboot、內(nèi)核還是程序;版本號用于和當(dāng)前系統(tǒng)進行版本的比較;不同硬件的程序包是不能混著升級的,故需要知道硬件型號。
????????3、某個客戶端正在升級程序時,又有其他客戶端升級程序,怎么辦?
????????最簡單的辦法:直接返回錯誤。
????????4、程序分區(qū)的大小是有限制的,程序包的大小超過了程序分區(qū)的大小,怎么辦?
????????認為程序包非法,終止升級流程,并返回錯誤碼給客戶端,由客戶端進行提示。
????????5、正在向程序分區(qū)寫入數(shù)據(jù)時,如果此時數(shù)據(jù)傳輸通道異常斷開了,怎么辦?
????????不要終止寫入,而應(yīng)當(dāng)繼續(xù)完成整個升級流程(只是不需要返回進度了)。如果此時終止數(shù)據(jù)寫入,已經(jīng)寫入的數(shù)據(jù)是不完整的,重啟后,程序?qū)o法正常運行。
????????6、向程序分區(qū)寫完所有數(shù)據(jù),并發(fā)送了100%的進度信息后,如果此時立即斷開數(shù)據(jù)傳輸通道,客戶端可能收不到進度為100%的信息,導(dǎo)致客戶端認為升級出錯了。怎么辦?
????????發(fā)送了100%的進度信息后,應(yīng)當(dāng)?shù)却欢螘r間(比如:3秒鐘)后,再跳出工作循環(huán)。在這段時間內(nèi),正常情況下,客戶端應(yīng)當(dāng)能收到進度為100%的信息,然后由客戶端斷開數(shù)據(jù)傳輸通道,嵌入式設(shè)備檢測到連接斷開后,跳出工作循環(huán)。
????????7、正在向程序分區(qū)寫入數(shù)據(jù)時,傳過來了重啟設(shè)備或關(guān)機的命令,怎么辦?
????????處理重啟設(shè)備或關(guān)機的命令時,如果發(fā)現(xiàn)正在升級程序,直接返回錯誤。
????????8、正在向程序分區(qū)寫入數(shù)據(jù)時,斷電了怎么辦?
?????????有以下幾種處理方式:
????????□ 不處理
????????上電后,嵌入式設(shè)備會變成“磚”,除非通過串口重新寫入程序。
????????□ 使用備份分區(qū)
????????當(dāng)空間足夠時,存在一個程序分區(qū)和一個備份程序分區(qū)。
????????(1)升級程序時,先寫入備份程序分區(qū),寫完并同步后,開始寫程序分區(qū),同時置正在升級標(biāo)志為true。寫完程序分區(qū)并同步后,置正在升級標(biāo)志為false,說明升級成功。
????????(2)在根文件系統(tǒng)分區(qū)或其他分區(qū)中,存在一個子程序。子程序運行后,檢查正在升級標(biāo)志。若發(fā)現(xiàn)為true,說明升級不成功,需要進行恢復(fù)。若發(fā)現(xiàn)為false,則檢查程序進程是否存在,若不存在,則也需要進行恢復(fù)。若不需要進行恢復(fù),則子程序直接退出。恢復(fù)的邏輯為:首先讀取備份程序分區(qū),并同步寫到程序分區(qū);然后,檢查正在升級標(biāo)志是否為true,若為true,則修改為false;最后重啟。
????????(3)升級程序時,檢查子程序進程是否存在,若存在,則直接返回失敗,避免同時讀寫備份程序分區(qū)。
????????□ 使用鏈接
????????(1)當(dāng)通過鏈接下載升級程序包時,可以將鏈接保存下來。
????????(2)子程序發(fā)現(xiàn)需要恢復(fù)時,從保存的鏈接下載程序包,再寫入程序分區(qū)。
總結(jié)
????????1、全面思考問題的前提條件是經(jīng)驗和積累,只有具備豐富的經(jīng)驗和充足的積累后,才能做到全面思考。因此,需要我們多拓展知識面的寬度,多挖掘知識面的深度。
????????2、全面思考問題時,需要考慮一個問題的所有影響因素,以及這些因素之間的關(guān)聯(lián)關(guān)系和相互作用。
????????3、多從各個角度、各個層面考慮問題,比如:從代碼規(guī)范的角度看有沒有遵守,從封裝的角度看合不合理,從邏輯的角度看嚴不嚴密,從效率的角度看還能否優(yōu)化,等等。
????????4、當(dāng)一種思維方式行不通或遇阻時,不要“鉆牛角尖”。多嘗試跳出這種思維方式,換一個角度,換一種思維方式思考和分析問題。
????????5、多反問自己:類里面的成員變量,都有正確賦初始值嗎?分配的內(nèi)存和創(chuàng)建的句柄,有正確釋放嗎?在當(dāng)前這個位置必須要釋放嗎,釋放完了其他地方還有沒有在使用?… …文章來源:http://www.zghlxwxcb.cn/news/detail-478418.html
????????6、多從全局和整體思考問題,這樣才能夠看到事物的來龍去脈,看到事物發(fā)展變化的趨勢及其背后的驅(qū)動因素。文章來源地址http://www.zghlxwxcb.cn/news/detail-478418.html
到了這里,關(guān)于軟件工程師,全面思考問題很重要的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!