C++编程风格
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;
}
}
}
}