《Code Aesthetic》教你简化代码
《Code Aesthetic》教你简化代码
《Code Aesthetic》是一系列关于代码美学和编程最佳实践的视频教程。本文总结了其中一些实用的建议,包括变量命名、代码复用、依赖注入、注释使用、嵌套消除等多个方面,帮助开发者写出更优雅、更易读的代码。
1. 命名方式(Naming Patterns)
如果不是特殊需要,变量名不要使用单个字母或者缩写。以前提倡缩写是为了节省打字时间,现在编辑器都有变量提示功能根本不需要缩写,习惯缩写会让你连前几天写的的代码都看不懂了!!更不要说给别人看。
为了提高代码可读性,应该在变量名中加入一些有用的信息,比如变量类型(如果是动态类型的语言如python、JS就很有必要,静态类型的语言如C++、Java就没必要)、数据类型(数组维度等)、时间单位等,根据不同情况来选择不同的命名元素
# 改进前
add_numbers = [1,2,3]
renderInterval = 0.1
# 改进后
add_numbers_list = [1,2,3]
renderIntervalSeconds = 0.1
变量名不要加入一些无用(抽象)的词,比如Base、Simple等,这些词汇对理解代码毫无作用。
2. 代码复用
继承(inherit)、组合(composition)都是为了代码复用,但是“组合优于继承”
继承的本质就是将父类复制给子类,然后在这基础上再添加新的内容或重写原有内容。但很多时候子类只需要父类中的一部分内容,这样的继承会产生大量冗余的脏复制(脏复制 dirty duplication)
组合的方式,是在需要使用共用代码的时候才调用它,这样可以将私有和公用的代码分离。
组合的方式还可以再优化,直接在类里面写一个变量来接受这个复用代码,这是就变成了依赖注入。
// 继承方式复用代码
class Image {
size image_size
save()
load()
crop()
resize()
}
class png:Image{
crop()
}
class jpg:Image{
resize()
}
// 组合方式复用代码
class Image{
size image_size
save()
load()
}
class png{
crop(Image image)
}
class jpg{
resize(Image image)
}
// 依赖注入复用代码
class Image{
size image_size
save()
load()
}
class png{
Image image;
png(Image image) {
this.image = image;
}
crop()
}
class jpg{
Image image;
png(Image image) {
this.image = image;
}
resize()
}
依赖注入(Dependency Injection)就是把要用到的东西(依赖)以接口的形式作为参数传进来(注入)。
// 接口
ImageFile file{
save()
load()
}
class ImageApp {
Image image = new Image();
ImageFile file; // 依赖
ImageApp(ImageFile file){
this.file = file; // 依赖注入
this.file.load(image);
}
show(){
// show the image
}
}
void main() {
my_file = ImageFile()
my_image_app = ImageApp(my_file)
my_image_app.show()
}
4. 抽象与耦合
代码的抽象(Abstraction)会带来耦合(coupling),高耦合度会导致代码难以修改,是很常见的现象,不要过分追求两全其美的代码,复用和耦合冲突时,根据实际需要来权衡。
5. 不写注释(Don't write comments)
如果一段代码需要写注释,说明你的代码写的不够清楚,还可以优化。写注释是好习惯,但是为了改代码的时候不用再麻烦地去改一遍注释,还是有必要精简一下注释的。另外,虽然我们有能力去检查代码错误,但是很难去检测注释错误(不过现在的AI可以)。
例子1:可以用注释来解释数字5的意义,但是更好的做法是定义一个变量来表示它。
#A status of 5 signals message sent
if status == 5
message.markSent()
# 不写注释,用变量解释
MESSAGE_SENT = 5
if status == MESSAGE_SENT
message.markSent()
例子2:用注释解释多个判断条件,这时可以使用多个变量来代替,也可以把判断条件写成函数。将条件写成变量不仅可以省略注释,还使得条件可以复用。
# 优化前
# You can update a message IF
# the current user is the author of the message and the message was delivered
# less than 5 minutes ago OR if the current user is an administrator
# You can also edit the message if the message wasn't delivered yet
if (message.user.id == current_user.id and(message.delivered_time() is None
or (datetime.now()-message.delivered_time()).seconds < 300))
or current user.type == User.Administrator):
message.update_text(text);
# 优化1
# 使用变量代替条件
FIVE_MINUTES = 5*60
user is author = message.user,id == current user.id
is_recent = message.delivered_time() is None or (datetime.now()- message.delivered_time()).seconds < FIVE_MINUTES
user_is_admin = current_user.type == User.Administrator
if(user_is_author and is_recent) or user_is_admin:
message.update_text(text);
# 优化2
# 写成函数形式
def can_edit_message(current user, message):
FIVE_MINUTES=5*60
user_is_author = message.user,id == current_user.id
is_recent = message.delivered_time()is None or (datetime.now() - message.delivered_time()).seconds < FIVE_MINUTES
user_is_admin = current_user.type == User.Administrator
return (user_is_author and is_recent) or user_is_admin
if can_edit_message(current_user, message):
message.update_text(text);
如果你代码写的好,代码比注释更有解释能力,直接看代码就能读懂,注释就没有必要了。不过多数情况下注释是很有必要的,比如为了性能优化写的很晦涩、或者使用了某些数学公式、代码的来源。
6. 不写嵌套(never nesting)
多层嵌套的代码难以阅读,有两种手段来消除嵌套:
第一种方法是把一部分代码提炼成一个单独的函数,但是会增加调用函数的开销(不过一般情况下这种开销不大)。另一方法是是调整条件的顺序,使函数尽快返回。
如下面把第二个分支写到前面,第一分支就可以从else解放出来。这种形式就是验证守护(validation gatekeeping),把执行函数的要求写在函数功能的前面。
// 优化前
int calculate(int bottom,int top)
{
if(top >= bottom)
{
int sum = 0;
for (int number =bottom;number <= top;number++)

{
sum += filterNumber(number);
}
return sum;
}
else
{
return 0;
}
}
// 优化后
int calculate(int bottom,int top)
{
// 验证守护
if(top < bottom)
{
return 0;
}
int sum = 0;
for (int number =bottom;number <= top;number++)
{
sum += filterNumber(number);
}
return sum;
}
7. 不要过早优化(Permature Optimization)
你认为可以更快的做法实际上可能更慢(过早的断定优化方案),要以实际测量来选择优化方案。
例子1:C++中的++i 比 i++要快一些,因为i++需要复制一遍值给当下的代码,执行完代码再在原来的变量上加1。既然如此,为什么不能只用++i呢?作者查看了两者的汇编代码,i++确实比++i多了几行代码,但是当开启优化时,两者代码是一样的。这时候只能实际跑一下来测试时间,最后作者发现i++确实慢了。
例子2:查询某个用户的场景下,使用集合(set)即散列来代替列表能够更快地查找,但是当用户数量少数,对比实际测量得到的时间,集合会比列表慢,这是因为列表在内存中是连续的,而集合可能会在内存中四处分配,降低了缓存命中率。