转载

如何编写富有表现力的代码?

随着程序员经验的增长,他们必然会学到越来越多的方法来解决同样的问题。

最开始关心的是简单性。我们可能只想尽可能地使用最简单最直接的方法来避免过度设计。但最简洁的解决方案不一定是最短的方案。

考虑完简单性后,接下来要考虑的是表现力。你应当时刻思考着,一个新人要深入研究你的代码到什么程度才能理解你的代码。

代码如诗。

写富有表达力的代码会有助于以后的程序员来理解代码的用途。有可能也会在以后给你自己带来帮助。它也可能帮助你更好地理解问题。认真思考如何定义和封装解决方案的组件通常能够帮助你更好地理解问题、得到更合理的方案。

“自注释代码”

“自注释代码”是通过代码的结构和方法及变量的命名来让大部分代码可以实现自描述。这是一个非常好的做法,可以省去很多注释。

1

2

3

$user = new User(); // 创建一个新的user对象

$user->loadFromSession(session); // 通过session更新user

if ($user->isAuthenticated()) { ... } // 如果user对象被授权……

然而,根据最近和我的一个朋友讨论中对我的启发,富有表现力的代码并不是注释的一个替代品–没有代码是可以完全实现“自描述的”。写代码的时候要尽可能地让代码具有表现力,同时在需要的时候也要及时注释。方法,函数和类总是需要有一个总结性的注释,就像Python 编程约定中提到的。

措辞

认真思考如何对变量和方法命名很重要。

不要简写

1

2

var uid = 10; // 离开上下文,我可能不会明白uid是代表什么意思

var userIdentifier = 10; // 这样更好一些

要明确

尽可能地使用具体和明确的名词来描述方法和函数:

1

2

var event; // 不好 -- 太宽泛

var newsLinkClickEvent; // 很好 -- 明确

封装

没有人喜欢阅读过程很长的程序。它会很难领会。而阅读一组短的封装好的方法调用会更加容易。如果你要更深入地钻研,那么直接查阅相关方法即可。

1

2

3

4

5

6

7

8

// 与向你展示我们如何更新user的全部细节相反

// 我们将这些封装在updateDetails的方法中

// 允许你能够迅速明白顶层的处理过程

function saveUserDetails(userStore, userDetails) {

   var user = new User();

   user.updateDetails(userDetails); // 对user的全部细节进行设置

   userStore.save(user); // 将user的数据转化为正确的格式,并保存在user保存区

}

你需要 else 吗?

使用很多if..else条件语句会使程序变得混乱。在很多情况下,为使得程序更加易读,else的部分可以使用一个单独的方法或者函数调用来进行封装:

1

2

3

4

5

6

// 使用else语句

if (user.permissionGroup == 'administrator' ) {

   article.delete();

} else {

   page.showError( "Sorry you don't have permission to delete this article" );

}

1

2

3

4

// 不使用else语句

if (!user.deleteArticle(article)) {

   page.showError( "Sorry you don't have permission to delete this article" );

}

在使用switch 语句或者多重if..else if语句的时候,你可以考虑使用不同类型来替换:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

class User {

   function deleteArticle($article) {

     $success = false ;

     if (

       user->permissionGroup == 'administrator'

       || user->permissionGroup == 'editor'

     ) {

       $success = $article->delete();

     }

     return $success;

   }

}

你可以通过设计专门的类型来删除掉这里的if语句:

1

2

3

4

5

6

7

8

trait ArticleDeletion {

   function deleteArticle($article) {

     return $article->delete();

   }

}

class Editor implements User { use ArticleDeletion; }

class Administrator implements User { use ArticleDeletion; }

注意,我谨慎地选择组成Administrator和Editor,而不是让Administrator继承自Editor。这样做能保持我的程序结构更加扁平和灵活。这是组合优于继承原则中的一个例子。

深度

鉴于封装能够让程序在更抽象的层面上容易理解,所以通过将分离的关注点不封装在一起来保持单一职责准则是很重要的。

例如,可以像下面这样写:

1

2

3

var user = new User();

user.UpdateFromForm(); // user对象从页面表格中导入数据

user.SaveToDatabase();

虽然这样写很短很清晰,但是也会带来两个问题:

  • 用户只能通过更深入的查阅代码才能找到一些基本信息,比如数据库类的名字,或者细节将存入哪些表中等等。
  • 如果我们想用另外一个数据库的实例,我们必须重新编辑用户类。这样做没有多大意义。

总之,你应当选择传递对象,而不是在每一个里面进行实例化:

1

2

3

4

5

6

var user = new User();

var userData = Request.Form;

var database = new DatabaseManager();

user.ImportData(userData);

database.Save(user);

这里代码行数更多,但却是更加清楚到底做了什么。这样更通用。

整洁性

时时对自己的代码进行格式,这样做能让代码更加易读。不用担心空白,但要谨慎地使用缩进来让自己的代码结构更加清晰。

如果有公认的代码风格指导,那么你应该遵循它。例如,PHP语言有FIG 标准。

然而,我认为也没有必要过分纠结于代码标准(我的想法有些变化)因为你永远不可能让所有人都完全用同样地方式进行编码。所以如果你像我一样,随时都觉得有必要对代码进行格式调整来保证符合繁琐的标准,那么你可能需要训练自己来摆脱这个习惯。只要你能读懂,那就让它保持原样。

删除已注释掉的代码

如果你正在使用版本控制系统(例如Git),那么实在没有必要保留大块的已注释掉的或者用不到的代码。你只需要删除掉,就能让你的代码库更加整洁。如果你确实还会再用到,那么你可以在版本控制历史中找到那些代码。

权衡

在表现力和简洁性之间,总是会需要一些权衡。

深度 vs. 封装

程序员总是希望在自己的对象中尽可能地让结构扁平化,这样就不用依靠不断重复查找父类来获取相关代码。但保持代码按逻辑单元进行封装也十分重要。

通过使用依赖注入或者特征/多重继承来实现组合优于继承原则,可以同时达到这两个目标。

专门的语法

在很多语言中,通常会有一些略微艰涩的结构能够减少时间。这些结构大多会存在可读性与效率间的权衡。

三元操作符与空值合并运算符

C#和PHP都有空值合并运算符:

1

2

var userType = user.Type ?? defaultType; // C#

$userType = $user->Type ?: $defaultType; // PHP

而且几乎所有语言都支持三元运算符:

1 var userType = user.Type != null ? user.Type : defaultType;

这两种结构都比if..else的结构更加简洁,但是相对而言,他们在语义上不够清晰,所以存在权衡取舍。个人来说,我觉得在像这样条件简单的情况下使用三元运算符没有问题。但是如果很复杂,那么你还是应当使用完整的if..else语句。

插件 / 库

举例来说,在C#语言代码中:

1

2

3

4

5

6

7

8

var brownFish;

foreach (var fish in fishes) {

   if (fish.colour == "brown") {

     brownFish = fish;

     break;

   }

}

可以使用Linq库简化为:

1

2

using System.Linq;

var brownFish = fishes.First(fish => fish.colour == "brown");

后面的一个更加简洁,而且也不会太难理解。但是它要求:

1. 了解Linq库;

2. 理解lambda表达式的原理。

我想在这个例子中,Linq解决方法是一定会被推荐的,因为它更加简洁,更具有表现力。即使另外的程序员不了解Linq,他们也能够非常轻松地理解学会,这同时也会增加他们的知识储备。

一次性变量

下面这样定义变量是不值得的:

1

2

var arrayLength = myArray.length;

for (var arrayIterator; arrayIterator < arrayLength; arrayIterator++) { ... }

在有些情况下,变量能够被用来添加有用的语义信息。例如:

1

2

var slideshowContainer = jQuery('main>.show');

slideshowContainer.startSlideshow();

正文到此结束
Loading...