C++編程風格
在前面的《半年工作總結》中也談過現在公司的代碼風格不佳,使得我們這種新加入成員去維護這樣的代碼顯得很吃力。另外我也不是計算機科班出身,本身代碼量很少,所以也沒形成自己的編程風格。這幾天看了《Google開源項目風格指南》,這個指南中倡導的風格還是很合理的,希望自己以后編程也能遵循這個格式。
下面通過這個例子來說明,問題來自於滴滴2017年校招,如下:
| 問題: |
|---|
| 小青蛙有一天不小心落入了一個地下迷宮,小青蛙希望用自己僅剩的體力值P跳出這個地下迷宮。為了讓問題簡單,假設這是一個n*m的格子迷宮,迷宮每個位置為0或者1,0表示這個位置有障礙物,小青蛙達到不了這個位置;1表示小青蛙可以達到的位置。小青蛙初始在(0, 0)位置,地下迷宮的出口在(0, m-1)(保證這兩個位置都是1,並且保證一定有起點到終點可達的路徑),小青蛙在迷宮中水平移動一個單位距離需要消耗1點體力值,向上爬一個單位距離需要消耗3個單位的體力值,向下移動不消耗體力值,當小青蛙的體力值等於0的時候還沒有到達出口,小青蛙將無法逃離迷宮。現在需要你幫助小青蛙計算出能否用僅剩的體力值跳出迷宮。 |
這個題目本身不難,可以有多種解法,我覺得最直觀的就是最短路勁算法(Dijkstra's algorithm),可分為以下步驟:
- 初始化,設點初始點,該初始點需消耗的生命值為0,也是找到的第一個離出發點消耗生命值最小的點;
- 根據目前找到的最優點,去更新要到達它上下左右點的生命值;
- 遍歷所有可達點,找到比上個最優點需要生命值大的最小點,如果該點為終止點或者到達該點需要生命值大於最大生命值則程序退出,否則跳動2,繼續執行。
代碼如下,我設計了一個MazeHelper類,該類對外開放三個函數,分別用來初始化、執行和輸出結果。
可能很多人會覺着這么簡單的一個問題一個函數就能搞定,何必弄得這么麻煩。當然,在做校招筆試題的時候不要這樣做,我這樣做是想去模擬實際的工程問題,如何編寫易讀性強、可維護性好的程序。這半年的工作經歷讓我覺得這個是非常重要的。還是以我們公司舉個例子吧,我們函數動輒好幾百行,最長的我見過2000+行的,而我就是在維護這樣的代碼。造成這種情況的原因是公司很多員工本事不是學計算機的,都是學物理、數學之類的,雖然他們很多都是名校畢業,但奈何沒有軟件開發的經驗,公司對這方面的工作也不是很重視。
《Clean Code》中說的broken window現象,即如果一個窗口玻璃破了,如果不及時修理,肯定會有人去打破更多的窗戶玻璃,然后往里面扔垃圾。軟件設計也是一樣,對於這樣的代碼你能如何去維護了,還不是直接去出問題的函數里面添加新的內容,所以函數只會越來越長。
所以在這個例子中,我遵循《Clean Code》中提倡的設計尊則:
- 函數越短越好;
- 每個函數只做一件事情;
- 少些comment,盡量使用好的變量名、函數名,讓code自己去描述(comment意味中code設計的失敗,code實在描述不出來了再寫comment,)
- comment是不可靠的,code is truth。
本文中實例代碼的風格可總結如下:
- 使用namespace,但不使用using namespace;
- 類名、函數名每個單詞首字母大寫;
- 變量名小寫,下划線分割,成員變量最后多加一個下划線區分;
- 有常量時使用枚舉;
- 分行代碼總寬不超過80個字符。
// mazehelper_test.cc
#include"mazehelper.h"
int main()
{
Maze::MazeHelper mazehelper;
std::ifstream fs("test.txt", std::fstream::in);
mazehelper.Initialize(fs);
mazehelper.Run();
mazehelper.PrintOptimalPath();
return 0;
}
// test.txt
4 4 10
1 0 0 1
1 1 0 1
0 1 1 1
0 0 1 1
// mazehelper.h
#ifndef MAZEHELPER_H
#define MAZEHELPER_H
#include<iostream>
#include<fstream>
#include<vector>
#include<utility>
#include<climits>
namespace Maze {
class MazeHelper {
public:
typedef std::pair<int, int> POINT;
enum Penalty{
PENALTY_LEFT = 1,
PENALTY_RIGHT = 1,
PENALTY_UP = 3,
PENALTY_DOWN = 0
};
void Initialize(std::istream& cin);
void Run();
void PrintOptimalPath();
private:
void UpdateNeighbor(int r, int c, int nr, int nc, int penalty);
void FindNextEasiestArrivePoint(int& r, int & c);
private:
int num_row_;
int num_column_;
int max_lifevalue_;
int current_max_lifevalue_;
std::vector<std::vector<int> > maze_;
std::vector<std::vector<int> > min_needed_lifevalue_;
std::vector<std::vector<POINT > > optimal_path_;
std::vector<std::vector<bool> > path_determined_flag_;
};
}
#endif
// mazehelper.cc
#include"mazehelper.h"
namespace Maze {
void MazeHelper::Initialize(std::istream& cin)
{
cin >> num_row_ >> num_column_ >> max_lifevalue_;
maze_.resize(num_row_);
min_needed_lifevalue_.resize(num_row_);
optimal_path_.resize(num_row_);
path_determined_flag_.resize(num_row_);
for (int i = 0; i < num_column_; ++i) {
maze_[i].resize(num_column_, 0);
min_needed_lifevalue_[i].resize(num_column_, INT_MAX);
optimal_path_[i].resize(num_column_, std::make_pair(INT_MIN, INT_MIN));
path_determined_flag_[i].resize(num_column_, false);
}
for (int i = 0; i < num_row_; ++i) {
for(int j = 0; j < num_column_; ++j) {
cin >> maze_[i][j];
}
}
}
void MazeHelper::Run()
{
int r = 0;
int c = 0;
min_needed_lifevalue_[0][0] = 0;
current_max_lifevalue_ = 0;
while ( (r != 0 || c != num_column_-1) &&
current_max_lifevalue_ <= max_lifevalue_) {
path_determined_flag_[r][c] = true;
UpdateNeighbor(r, c, r-1, c, PENALTY_UP);
UpdateNeighbor(r, c, r+1, c, PENALTY_DOWN);
UpdateNeighbor(r, c, r, c-1, PENALTY_LEFT);
UpdateNeighbor(r, c, r, c+1, PENALTY_RIGHT);
current_max_lifevalue_ = INT_MAX;
FindNextEasiestArrivePoint(r, c);
}
}
void MazeHelper::UpdateNeighbor(int r, int c, int nr,
int nc, int penalty)
{
if (nr < 0 || nr >= num_row_ || nc < 0
|| nc >= num_column_ || 0 == maze_[nr][nc]) {
return;
}
int lifevalue_old = min_needed_lifevalue_[nr][nc];
int lifevalue_new = min_needed_lifevalue_[r][c] + penalty;
if (lifevalue_new < lifevalue_old) {
min_needed_lifevalue_[nr][nc] = lifevalue_new;
optimal_path_[nr][nc] = std::make_pair(r,c);
}
}
void MazeHelper::FindNextEasiestArrivePoint(int& r, int& c)
{
for (int i = 0; i < num_row_; ++i) {
for (int j = 0; j < num_column_; ++j) {
if(!path_determined_flag_[i][j] &&
min_needed_lifevalue_[i][j]
< current_max_lifevalue_) {
current_max_lifevalue_ =
min_needed_lifevalue_[i][j];
r = i;
c = j;
}
}
}
}
void MazeHelper::PrintOptimalPath() {
if (current_max_lifevalue_ > max_lifevalue_) {
std::cout << "path doesn't exist\n";
} else {
POINT pt = std::make_pair(0, num_column_ - 1);
std::vector<POINT> path;
while (pt.first != 0 || pt.second != 0) {
path.push_back(pt);
pt = optimal_path_[pt.first][pt.second];
}
path.push_back(std::make_pair(0, 0));
for(int k = path.size()-1; k >= 0; --k) {
std::cout << "(" << path[k].first << "," << path[k].second
<< ")" << std::endl;
}
}
}
}
