早些年,我特别喜欢下围棋,每天都会下几盘。那时候日本围棋不仅高手林立,而且风格迥异,比如:小林光一的地铁流,武宫正树的宇宙流等等,不过我最喜欢的棋手当属大竹英雄,他下棋时追求美感,如果棋形不漂亮,那么他宁可认输也绝不玷污棋盘。后来,我成为了一名程序员,每天都要写不少代码,可惜写了不少丑陋的代码,本文筛选了几个例子,希望大家看过之后都能写出更具美感的代码来。
在聊之前,我们不妨想想割裂到底是什么意思。其实所谓割裂,说白了就是指把原本应该在一起的东西分开了,比如两地分居的夫妻,亦或者留守儿童和爸爸妈妈。
普通的语言描述总是苍白无力的,让我们看看有割裂感的代码到底长啥样:
<?php $i = 0; while ($i <= 10) { echo $i; $i++; } ?>
代码似乎很常见,如果你没有意识到割裂感的存在,请看看下面的改进版:
<?php for ($i = 0; $i <= 10; $i++) { echo $i; } ?>
怎么样?理解了吧!在我们的努力下,变量「i」的三口之家终于团聚了!当然,这只是命令式语言的写法,如果你想更酷一点,还可以用函数式语言的写法:
<?php array_map(function($i) { echo $i; }, range(0, 10)); ?>
接着看另一个例子,如果想统计程序的运行时间,那么最简单的方法无疑是开头记录一下起始时间,最后记录一下结束时间,然后相减,就好像下面这样:
<?php // head $begin = microtime(true); // mock sleep(1); // foot $end = microtime(true); echo $end - $begin; ?>
可惜如此一来前后时间相关代码无疑就两地分居了,想想看如何让它们团聚:
<?php $begin = microtime(true); register_shutdown_function(function() use($begin) { $end = microtime(true); echo $end - $begin; }); sleep(1); ?>
BTW:当然,这用到了一下 PHP 本身的语言特性,如果放到其它语言里,也可以利用装饰器模式等方法来达到类似的效果,具体实现这里就不多说了。
最后再看一个例子,很多项目都会搞一个类统一存放错误码,如果业务出错了就返回相应的错误码,然后抛出相应的异常信息,大致代码如下所示:
<?php class Result { const ERROR_USERNAME = 1; const ERROR_PASSWORD = 2; public static $msg = array( 1 => '用户名错误', 2 => '密码错误', ); } ?>
可惜,错误码和错误信息割裂了。如此说来,改成数组怎么样:
<?php class Result { public static $msg = array( 'ERROR_USERNAME' => '用户名错误', 'ERROR_PASSWORD' => '密码错误', ); } ?>
不过这样数字标识就没有了,很多时候我们需要它,而且放弃了 const,也就意味不能再享受到对应的工具提示,错误检查。其实新版 PHP 的 const 可以赋值数组:
<?php class Result { const ERROR_USERNAME = [1, '用户名错误']; const ERROR_PASSWORD = [2, '密码错误']; } ?>
两地分居的代码终于又团聚了!使用的时候只要抛出一个精心构造的异常就行了:
<?php class ResultException extends Exception { public function __construct(array $result = array()) { if ($result) { parent::__construct($result[1], $result[0]); } } } throw new ResultException(Result::ERROR_USERNAME); ?>
结尾顺便说一句,搞一个类单独存放错误码是否可取存争议,通常我们认为这样的类是哑巴类,相对应的,错误码应该分散到它原本所属的业务对象之中去。不过这个问题和本文的主题关系不大,说起来就没完没了了,索性留到以后有空再聊。
赞,抛出异常这种上面定义错误码,下面定义错误信息的方式体验确实很不好。错误码多的话,要隔着好多行,找对应的错误信息。
Pingback引用通告: 注意代码割裂感 | 冷静的博客
处理得很漂亮,虽然只是一些小细节,然而细节见真章
class Result
{
const ERROR_USERNAME = [1, ‘用户名错误’];
const ERROR_PASSWORD = [2, ‘密码错误’];
}
你说的我都懂 可是 你访问一个具体错误值的时候 ERROR_USERNAME 到底是 1 还是用户名错误呢?
这样使用Result::ERROR_USERNAME[0]
Result::ERROR_USERNAME[1]
我也觉得这样有点问题,比如当需要根据 1 2 这样的数字来获取对应的字符串提示的时候就还需要遍历数组。
多点儿成本,用
const ERR_USERNAME = ‘1:用户名错误’
深度好文,课件博主功力真深厚
割裂的代码确实更不容易理解,而且很有可能在修改时引入bug。
《编写可读代码的艺术》里提到,代码可以按照目的分块,不同目的的代码放到同个代码块,可以让代码可读性更好且更容易维护。
Pingback引用通告: 如何编码事务 – 酷博客
Pingback引用通告: 如何编码事务-IT技术资讯