斯巴达编程
我真的很喜欢Jeff关于Spartan编程的文章。我同意这样的代码是一种阅读的乐趣。不幸的是,我不确定与之合作一定会很有趣。
多年来,我已经阅读并坚持"每行一个表达式"的实践。当许多编程书籍都通过以下示例代码来反驳此建议时,我就奋勇拼搏并坚守立场:
while (bytes = read(...)) { ... } while (GetMessage(...)) { ... }
最近,出于更实际的原因,我提倡每行一个表达式,以进行调试和提供生产支持。从生产中获取日志文件,该日志文件在"第65行"处声明NullPointer异常,其内容为:
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());
令人沮丧并且完全可以避免。缺少使用可以选择空值"最可能"对象的代码的专家的想法……这确实是一个现实的痛苦。
在单步执行代码时,每行一个表达式也有很大帮助。我以大多数现代编译器可以优化我刚刚创建的所有多余的临时对象为前提来进行练习...
我尝试保持整洁,但是用显式对象弄乱我的代码有时会很费力。它通常不会使代码更易于浏览,但是在追查生产中的内容或者逐步浏览本人或者其他人的代码时,它确实非常有用。
我们主张采用哪种风格,并且可以从实际意义上使其合理化?
解决方案
回答
每行一个表达式。
没有理由混淆代码。键入一些额外的术语会花费额外的时间,从而节省了调试时间。
回答
我认为理想的解决方案是在极端之间找到平衡。没有一种方法可以写出适合所有情况的规则。它带有经验。在自己的行上声明每个中间变量将使读取代码更加困难,这也将增加维护难度。同样,如果内联中间值,调试会更加困难。
"最佳位置"位于中间位置。
回答
我倾向于在可读性方面,不一定在可调试性方面。绝对应该避免使用我们给出的示例,但是我认为明智地使用多个表达式可以使代码更简洁和易于理解。
回答
在实用程序员亨特(Hunt)和托马斯(Thomas)谈及一项研究时,他们称其为Demeter法则,它着重于功能与模块之间的耦合。通过使函数永远不会达到耦合的第三级,可以显着减少错误数量并提高代码的可维护性。
所以:
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());
接近重罪,因为我们在鼠洞下方有4个物体。这意味着要更改其中一个对象中的某些内容,我必须知道我们正是在这种方法中调用了整个堆栈。真痛苦
更好的:
Account.getUser();
注意,这与现在在模拟软件中非常流行的编程形式相反。折衷方案是无论如何我们都拥有紧密耦合的接口,而富有表现力的语法使其更易于使用。
回答
我通常处于"越短越好"的阵营。示例很好:
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());
如果我看到超过四行而不是一行,我会感到畏缩-我认为这不会使阅读或者理解更加容易。我们在这里展示它的方式很显然是在挖掘单个对象。这不是更好:
obja State = session.getState(); objb Account = State.getAccount(); objc AccountNumber = Account.getAccountNumber(); ObjectA a = getTheUser(AccountNumber);
这是一个折衷方案:
objb Account = session.getState().getAccount(); ObjectA a = getTheUser(Account.getAccountNumber());
但我仍然更喜欢单行表达式。这是一个轶事原因:对于我来说,现在很难重读和错误检查4衬套是否有错字;单行不存在此问题,因为仅会有更少的字符。
回答
可维护性以及可读性是至上的。幸运的是,更短通常意味着更易读。
这是我喜欢用来对代码进行切片和切块的一些技巧:
- 变量名称:我们将如何向团队中的其他人描述此变量?我们不会说" numberOfLinesSoFar整数"。我们会说" numLines"或者类似内容-易于理解和简短。不要假装维护人员根本不了解代码,但要确保自己能弄清楚变量是什么,即使我们忘记了自己编写该变量的行为。是的,这很明显,但是它比我看到的许多编码器要花更多的精力,所以我首先列出它。
- 控制流:一次避免大量关闭子句(C ++中一系列})。通常,当我们看到此消息时,有一种避免它的方法。常见的情况是
:
if (things_are_ok) { // Do a lot of stuff. return true; } else { ExpressDismay(error_str); return false; }
可以替换为
if (!things_are_ok) return ExpressDismay(error_str); // Do a lot of stuff. return true;
如果我们可以让ExpressDismay(或者其包装)返回false。
另一种情况是:
- 循环迭代:标准越多越好。对于较短的循环,最好在不使用变量(仅将其用作单个对象的索引)时使用单字符迭代器。
我在这里要争论的特殊情况是反对使用STL容器的"正确"方法:
for (vector<string>::iterator a_str = my_vec.begin(); a_str != my_vec.end(); ++a_str)
复杂得多,并且需要在循环中重载指针运算符* a_str或者a_str-> size()。对于具有快速随机访问的容器,以下内容更容易阅读:
for (int i = 0; i < my_vec.size(); ++i)
在循环体中引用my_vec [i],不会混淆任何人。
最后,我经常看到编码人员以行号计数为荣。但是,不是行号才重要!我不确定实现此目标的最佳方法,但是如果我们对编码文化有任何影响,我会尝试将奖励转移给紧凑型类的开发者:
回答
很好的解释。我认为这是一般"分而治之"心态的版本。
回答
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());
这是一个不好的例子,可能是因为我们只是从头顶写了一些东西。
我们正在将名为" getTheUser"的函数的返回值分配给类型为" ObjectA"的名为" a"的变量。
因此,假设我们写的是:
User u = getTheUser(session.getState().getAccount().getAccountNumber());
我会像这样破坏这个表达:
Account acc = session.getState().getAccount(); User user = getTheUser( acc.getAccountNumber() );
我的理由是:我将如何考虑此代码的工作?
我可能会想:"首先,我需要从会话中获取帐户,然后使用该帐户的号码获取用户"。
该代码应按照想法进行阅读。变量应引用所涉及的主要实体;与其属性无关(因此我不会将帐号存储在变量中)。
要记住的第二个因素是:在这种情况下,我是否需要再次引用该实体?
例如,如果我要从会话状态中抽出更多东西,那么我将引入SessionState state = session.getState()
。
这一切似乎都是显而易见的,但恐怕我很难说出为什么有意义,而不是说英语为母语的人。