我非常喜欢Jeff's post上的Spartan Programming。我同意这样的代码是一种阅读的乐趣。不幸的是,我不太确定与之合作一定会很愉快。
多年来,我一直在阅读并遵守“一行一线”的做法。当许多编程书籍用例如下面的示例代码来反驳这个建议时,我已经打了很好的战斗并坚守了基础。
while (bytes = read(...))
{
...
}
while (GetMessage(...))
{
...
}
最近,出于更实际的原因,我提倡每行一个表达式 - 调试和生产支持。从生产中获取一个声明NullPointer异常的日志文件在“第65行”,其中包含:
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());
令人沮丧,完全可以避免。没有抓住专家的代码可以选择“最有可能”的对象是空的......这是一个真正的实际痛苦。
每行一个表达式在步进代码时也有很大帮助。我是在假设大多数现代编译器可以优化掉我刚刚创建的所有多余的临时对象的情况下练习的......
我试着整洁 - 但是用明确的对象混乱我的代码确实有时会感到费力。它通常不会使代码更容易浏览 - 但在生产中跟踪事物或者通过我或他人的代码时,它确实派上了用场。
你提倡什么样的风格,你能否在实际意义上理顺它?
答案 0 :(得分:7)
在实用程序员中,亨特和托马斯谈论了一项他们称之为得墨忒耳法的研究,并着重于功能与模块之外的模块的耦合。通过允许函数永远不会达到其耦合的第3级,您可以显着减少错误数量并提高代码的可维护性。
所以:
ObjectA a = getTheUser(session.getState()。getAccount()。getAccountNumber());
接近重罪是因为我们是鼠洞下面的4个物体。这意味着要改变其中一个对象中的某些东西我必须知道你在这个方法中调用了整个堆栈。多么痛苦。
更好:
Account.getUser();
请注意,这与现在非常受模拟软件欢迎的富有表现力的编程形式背道而驰。无论如何,你需要有一个紧密耦合的界面,而富有表现力的语法使它更容易使用。
答案 1 :(得分:5)
我认为理想的解决方案是在极端之间找到平衡点。没有办法写出适合所有情况的规则;它带来了经验。在自己的行上声明每个中间变量将使得读取代码变得更加困难,这也将导致维护上的困难。出于同样的原因,如果你内联中间值,调试就会困难得多。
“甜蜜点”位于中间位置。
答案 2 :(得分:4)
每行一个表达式。
没有理由混淆您的代码。您输入额外几个术语的额外时间,可以节省调试时间。
答案 3 :(得分:3)
我倾向于在可读性方面犯错,不一定是可调试性。你应该避免使用你给出的例子,但我觉得明智地使用多个表达式可以使代码更加简洁和易于理解。
答案 4 :(得分:2)
我通常在“越短越好”阵营。你的榜样很好:
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-liner现在的愚蠢错别字;单行没有这个问题,因为只有更少的字符。
答案 5 :(得分:2)
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());
这是一个不好的例子,可能是因为你刚从头顶写下了什么。
您正在为类型为a
的名为ObjectA
的变量分配名为getTheUser
的函数的返回值。
所以我们假设你写了这个:
User u = getTheUser(session.getState().getAccount().getAccountNumber());
我会像这样打破这个表达式:
Account acc = session.getState().getAccount();
User user = getTheUser( acc.getAccountNumber() );
我的理由是:我如何考虑使用此代码进行操作? 我可能会想:“首先我需要从会话中获取帐户然后让用户使用该帐户的号码”。
代码应该按照您的想法阅读。变量应指所涉及的主要实体;与其属性无关(因此我不会将帐号存储在变量中)。
要记住的第二个因素是:在这种情况下,我是否需要再次引用此实体?
如果,比方说,我将更多东西拉出会话状态,我会介绍SessionState state = session.getState()
。
这一切看起来都很明显,但我担心为什么它有意义,而不是母语为英语的人都会有一些困难。
答案 6 :(得分:0)
可维护性,以及可读性,是王道。幸运的是,缩短通常意味着更具可读性。
以下是我喜欢使用切片和骰子代码的一些提示:
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-&gt; size()。对于具有快速随机访问的容器,以下内容更容易阅读:
for (int i = 0; i < my_vec.size(); ++i)
在循环体中引用my_vec [i],这不会混淆任何人。
最后,我经常看到编码员为他们的行号计数感到自豪。但重要的不是行数!我不确定实现这一点的最佳方法,但是如果你对你的编码文化有任何影响,我会尝试将奖励转移到那些紧凑类的人:)
答案 7 :(得分:0)
很好的解释。我认为这是一般Divide and Conquer心态的版本。