共计 7997 个字符,预计需要花费 20 分钟才能阅读完成。
大家好,我是多选参数的一员 —— 大炮。
在上一篇 可读代码炸鸡二 (下篇) – 命名的歧义 的结尾处,提到了接下来的炸鸡会围绕 多行代码,多个函数 的代码范畴来探讨代码可读性的优化。
由这个思路走的话,那么接下来的炸鸡大抵会分成两个内容:
- 代码审美
- 代码正文
所以本篇炸鸡将探讨 代码审美 对于可读性的作用。
准则
- 统一布局,让读者很快习惯。
- 类似代码看起来要类似。
- 相干代码分组分块。
须要辨别于设计
这只是代码外观组织上的优化,并非代码外部逻辑构造的重构优化。
然而审美上的要求在肯定水平上会影响到代码外部逻辑构造。
为啥审美重要
喏,实际出真知。显著如下代码就是很差劲的代码编写。
class hamBurgerShop {
public:
// 敌无不斩,斩无一直
void Add(double d); // 减少一个货色
private: int count; /* 多少
*/ public:
double Average();
private: double minimum;
list<double>
past_items
;double maximum;
};
而后做一点审美层面的批改,你再看看,可读性霎时进步。
// 汉堡店类 - 其实这个正文不必加。class hamBurgerShop {
public:
void Add(double d);
double Average();
private:
list<double> past_items;
int count; // 多少个汉堡
double minimum;
double maximum;
};
代码中提到了一件事,也作为一个问题抛给大家。
为什么汉堡店类不须要正文?
这个问题会留到下一篇或者下下一篇炸鸡给出一些见解。
换行要统一且紧凑
首先咱们构想一下这样的代码,生成两个 Fort
对象。但因为代码过长,可能会让你一直地使光标向右来查看残缺代码(当然即便应用 vim
也得一直地按键),不能让阅读者同屏浏览代码,减少阅读者累赘。
local fort1 = Fort.new("k1", 2222, 300, 4000, true, true)
local fort2 = Fort.new("super kingdom fort", 22256, 300, 4400, true,false, ...) -- 更加地长
if xxx then
Log("xxx", shellNum)
end
还有《The Art of Readable Code》中提到的例子,同样是代码过长造成的浏览累赘。
public class PerformanceTester {public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(500,80, 200, 1);
public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator(500,80, 0, 0);
}
所以这时候换行就体现出作用了。
local fort1 = Fort.new("k1", 2222, 300, 4000, true, true)
local fort2 = Fort.new("super kingdom fort", 22256,
300, 4400, true,false, ...)
// 一种换行
public class PerformanceTester {
public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(500,
80, 200, 1);
public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator(500,
80, 0, 0);
}
// 又一种换行
public class PerformanceTester {
public static final TcpConnectionSimulator wifi
= new TcpConnectionSimulator(500,80, 200, 1);
public static final TcpConnectionSimulator t3_fiber
= new TcpConnectionSimulator(500,80, 0, 0);
}
这样便将代码长度管制在可视范畴内。然而雷同作用的两行代码,却不肯定能体现雷同的参数地位,这样也会对阅读者造成肯定影响。所以咱们能够将换行变得 统一且紧凑。
local fort1 =
Fort.new(
"k1",
2222,
300,
4000,
true,
true)
local fort2 =
Fort.new(
"super kingdom fort",
22256,
300,
4400,
true
false,
...)
public class PerformanceTester {
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
1 /* packet loss % */);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
45000, /* Kbps */
10, /* millisecs latency */
0, /* jitter */
0 /* packet loss % */);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(
100, /* Kbps */
400, /* millisecs latency */
250, /* jitter */
5 /* packet loss % */);
}
当然,这在肯定水平上减少了代码行数,所以不要适度应用这样的规定。同时如果须要屡次应用这样的办法,是否须要思考一下,你的代码外部是否须要优化?是不是代码外部成为了宏大的 超级工厂?
你能够抉择优化代码构造,抽出更多的函数,或者可能须要肯定的设计模式的内容,感兴趣的敌人能够自行查阅相干书籍与材料。
回忆一下结尾的准则,类似的类似,统一的布局。
同时须要提一嘴的是:
这个正文存在的意义是为参数退出更多的信息,例如上述代码是为了通知阅读者参数的单位值。若参数命名表白的信息足够残缺,不必减少这样的正文。当然这个问题在后头的炸鸡中我会具体阐明。
然而这个正文和换行过于反复。咱们能够将阐明信息放到顶部,具体的如下所示。将函数名和参数列出,同时还列出参数具体意思。
public class PerformanceTester {// TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
// [Kbps] [ms] [ms] [percent]
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(500, 80, 200, 1);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(45000, 10, 0, 0);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(100, 400, 250, 5);
}
用封装办法来革除不太好看的货色
什么叫做不太好看?
例如到处应用反复的代码。
function giveItem(itemId, amount, srcUserUid, destUserUid, needSync)
end
function Map:addNeedFood2user(uid)
local count = 0
for _, city in pairs(self._cities) do
if exp1 then count = count + 1 end
end
local needFood = self:baseFood() * 0.8 + count ^ 2 *0.12
call2User(uid, "addFood", needFood)
end
function Map:isNeedExpand()
local count = 0
for _, city in pairs(self._cities) do
if exp1 then count = count + 1 end
end
if count > 400 then
return true
else
return false
end
end
咱们能够看到,这一个逻辑是都有用到的。
local count = 0
for _, city in pairs(self._cities) do
if exp1 then count = count + 1 end
end
所以咱们将这个逻辑封装和形象,不便阅读者了解,同时不便了复用。
function Map:fetchCityNum()
local count = 0
for _, city in pairs(self._cities) do
if exp1 then count = count + 1 end
end
return count
end
function Map:addNeedFood2user(uid)
local count = self:fetchCityNum()
local needFood = self:baseFood() * 0.8 + count ^ 2 *0.12
call2User(uid, "addFood", needFood)
end
function Map:isNeedExpand()
local count = self:fetchCityNum()
if count > 400 then
return true
else
return false
end
end
咱们再举一个例子。
如果多个雷同函数执行,然而因为参数长短问题,使得代码整体,有的换行,有的太短,参差不齐。
我间接应用《The Art of Readable Code》的示例:
DatabaseConnection database_connection;
string error;
assert(ExpandFullName(database_connection, "Doug Adams", &error)
== "Mr. Douglas Adams");
assert(error == "");
assert(ExpandFullName(database_connection, "Jake Brown", &error)
== "Mr. Jacob Brown III");
assert(error == "");
assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");
assert(error == "no match found");
assert(ExpandFullName(database_connection, "John", &error) == "");
assert(error == "more than one result");
所以能够多封装一层函数,将代码规整。
void CheckFullName(string partial_name,
string expected_full_name,
string expected_error) {
// database_connection is now a class member
string error;
string full_name = ExpandFullName(database_connection, partial_name, &error);
assert(error == expected_error);
assert(full_name == expected_full_name);
}
这样先前繁冗代码能够变成如下清新的代码。
CheckFullName("Doug Adams", "Mr. Douglas Adams", "");
CheckFullName("Jake Brown", "Mr. Jake Brown III", "");
CheckFullName("No Such Guy", "","no match found");
CheckFullName("John", "","more than one result");
这样不仅去除了反复的代码,而且浏览代码的时候高深莫测,减少新的一行代码只有如法炮制即可。
所以上述例子或多或少证实了如果编写者对于代码审美做了要求,那么在 肯定水平上会优化代码的组织。
善用列对齐
列对齐针对多个反复的函数的参数列表,能够起到代码规整的作用。如上文的多行 CheckFullName
函数,通过列对齐的润饰,会更加分明:
CheckFullName("Doug Adams" , "Mr. Douglas Adams" , "");
CheckFullName("Jake Brown", "Mr. Jake Brown III" , "");
CheckFullName("No Such Guy" , "","no match found");
CheckFullName("John" , "","more than one result");
或者多列赋值的时候,也能够起到同样作用。
details = request.POST.get('details')
location = request.POST.get('location')
phone = request.POST.get('phone')
当然,一些简单数据结构的赋值,也能够利用。
local t = {{"timeout", 1, "shshshsh"},
{"timeout", 2, "hsh"},
{"ti", 3, "shshshsh"},
{"tries", 4, "shsh"},
...
}
用一个程序来写代码
- 最重要的语句最上,而后顺次依据重要性的递加来排列语句。
- 依照字母程序来排列
- 如果代码中提到了 A, B, C 三种命名,如果在同一个范畴内 (例如函数) 再次应用了它们,调用程序也保持一致。
第三种集体感觉较为广泛。我来解释一下。
如果一个代码须要三个步骤执行:
function test()
...
...
firstStep:start()
secondStep:start()
thirdStep:start()
end
那么他们的相干变量的申明也依据这个程序进行申明。
function test()
local firstStep = first:new()
local secondStep = second:new()
local thirdStep = third:new()
...
...
firstStep:start()
secondStep:start()
thirdStep:start()
end
分层或者分组思维
人更承受好的分组和档次,代码的过分堆砌,会使得浏览了解不太不便。例如下方列出的代码,看着不免有点费劲:
class FrontendServer {
public:
FrontendServer();
void ViewProfile(HttpRequest* request);
void OpenDatabase(string location, string user);
void SaveProfile(HttpRequest* request);
string ExtractQueryParam(HttpRequest* request, string param);
void ReplyOK(HttpRequest* request, string html);
void FindFriends(HttpRequest* request);
void ReplyNotFound(HttpRequest* request, string error);
void CloseDatabase(string location);
~FrontendServer();};
许多人编写程序都有一个 模块化的习惯,封装的形式取决于屡次呈现或者说可复用的代码量。
代码量 | 小 | 中 | 大 |
---|---|---|---|
封装形式 | 函数 | 类 | 模块 |
所以分层分组的思维,这其实就是将代码的编写逻辑分模块。是在代码量更小的状况下,不至于回升至封装为函数的时候,对代码逻辑的的一个模块化,让 A 块代码做的事件为一组,B 块代码做的事件为一组。
这个思维次要体现在多个变量申明,函数定义的时候,例如类的定义。利用正文和分段,将不同组或者说不同档次的申明离开。
class FrontendServer {
public:
FrontendServer();
~FrontendServer();
// Handlers
void ViewProfile(HttpRequest* request);
void SaveProfile(HttpRequest* request);
void FindFriends(HttpRequest* request);
// Request/Reply Utilities
string ExtractQueryParam(HttpRequest* request, string param);
void ReplyOK(HttpRequest* request, string html);
void ReplyNotFound(HttpRequest* request, string error);
// Database Helpers
void OpenDatabase(string location, string user);
void CloseDatabase(string location);
};
同样,利用 正文和分段,对一段逻辑代码进行总结和分段。
一开始代码如下:
def make_a_hamburg():
vegetable = Vegetables()
meat = Meat()
bread = Bread()
kitchen = Kitchen()
kitchen.wash(vegetable)
kitchen.cook(bread)
kitchen.cook(meat)
return kitchen.get_a_hamburg()
批改的代码如下,找的例子确实有点好玩,然而就是讲的这个分档次和总结的意思。
def make_a_hamburg():
# 筹备食材
vegetable = Vegetables()
meat = Meat()
bread = Bread()
# 找好厨房
kitchen = Kitchen()
# 开始制作
kitchen.wash(vegetable)
kitchen.cook(bread)
kitchen.cook(meat)
# 做好一个汉堡
return kitchen.get_a_hamburg()
当然,这个办法容易使得空行比拟多。我主程就要求咱们尽量不要有空行,放弃紧凑。
我集体感觉,如果可能很好地将代码有层次性地离开,多加空行是能够的;但如果加空行并不能达到这个成果,那只是徒增阅读者麻烦。
所以最终还是就 是否可读性,使得阅读者了解更快了解 这一规范掂量。
个人风格和我的项目格调一致性
Consistent style is more important than the「right」style.
当然,每个我的项目有都有本人规定的一套格调,即便可能不太正确,然而尽量和这个格调保持一致。
放弃我的项目格调统一,比保持个人风格更重要,不然芜杂在一起,看的更好受。
小结
- 如果类似代码屡次呈现,就要思考封装它们。
- 代码中变量的申明程序能够依照首字母程序,重要水平来排列。当然,还有自定义的程序,如果是自定义,这就须要申明的变量的程序和调用变量的程序尽量保持一致,体现出自定义程序的谨严。
- 善用正文和空行来将代码根据逻辑,性能作用等,进行分层,是一种更微型的模块化。
- 哦对了,列对齐能够规整多行赋值语句,还能够阐明函数作用和参数信息。
最初吟唱