9

我真的很喜欢JeffSpartan Programming上的帖子。我同意这样的代码阅读起来很有趣。不幸的是,我不太确定与它一起工作是否一定是一种乐趣。

多年来,我一直阅读并坚持“一行一个表达式”的做法。当许多编程书籍用示例代码反驳这个建议时,我已经打了一场好仗并坚持了自己的立场:

while (bytes = read(...))
{
   ...
}

while (GetMessage(...))
{
   ...
}

最近,出于更实际的原因,我提倡每行一个表达式 - 调试和生产支持。从生产中获取一个在“第 65 行”声明 NullPointer 异常的日志文件,内容如下:

ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());

令人沮丧并且完全可以避免。没有使用可以选择“最有可能”为空对象的代码的专家......这是一个真正的实际痛苦。

在单步执行代码时,每行一个表达式也有很大帮助。我假设大多数现代编译器可以优化掉我刚刚创建的所有多余的临时对象。

我尽量保持整洁——但是用显式对象来弄乱我的代码有时确实会让人感到费力。它通常不会使代码更易于浏览 - 但在跟踪生产中的内容或单步执行我或其他人的代码时,它确实派上了用场。

提倡什么风格,你能在实际意义上合理化它吗?

4

8 回答 8

7

The Pragmatic Programmer Hunt 和 Thomas 讨论了一项他们称之为 Demeter 定律的研究,它侧重于将功能与模块以外的模块耦合。通过允许一个函数在它的耦合中永远不会达到第 3 级,您可以显着减少错误的数量并提高代码的可维护性。

所以:

ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());

接近重罪,因为我们在老鼠洞里有 4 个物体。这意味着要更改其中一个对象中的某些内容,我必须知道您在此方法中调用了整个堆栈。多么痛苦。

更好的:

Account.getUser();

请注意,这与现在在模拟软件中非常流行的表达形式的编程背道而驰。代价是无论如何你都有一个紧密耦合的接口,而富有表现力的语法只是让它更容易使用。

于 2008-09-09T03:26:01.690 回答
5

我认为理想的解决方案是在极端之间找到平衡。没有办法编写适合所有情况的规则;它带有经验。将每个中间变量声明在自己的行中会增加阅读代码的难度,这也会造成维护的困难。出于同样的原因,如果您内联中间值,调试会更加困难。

“甜蜜点”在中间的某个地方。

于 2008-09-09T02:46:22.207 回答
4

每行一个表达式。

没有理由混淆您的代码。输入几个额外术语所花费的额外时间,可以节省调试时间。

于 2008-09-09T02:08:19.727 回答
3

我倾向于在可读性方面犯错,不一定是可调试性。您提供的示例绝对应该避免,但我认为明智地使用多个表达式可以使代码更简洁和易于理解。

于 2008-09-09T03:01:43.217 回答
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 是否存在愚蠢的错别字;单行没有这个问题,因为字符更少。

于 2008-09-09T03:44:03.100 回答
2
ObjectA a = getTheUser(session.getState().getAccount().getAccountNumber());

这是一个不好的例子,可能是因为你只是从头顶写了一些东西。a您正在为名为type的变量分配名为ObjectA的函数的返回值getTheUser
所以让我们假设你写了这个:

User u = getTheUser(session.getState().getAccount().getAccountNumber());

我会像这样打破这个表达:

Account acc = session.getState().getAccount();
User user = getTheUser( acc.getAccountNumber() );

我的理由是:我将如何看待我正在使用这段代码做什么?
我可能会想:“首先我需要从会话中获取帐户,然后我让用户使用该帐户的号码”。

代码应该按照您的想法阅读。变量应指涉及的主要实体;与其属性无关(所以我不会将帐号存储在变量中)。

要记住的第二个因素是:在这种情况下,我是否需要再次引用该实体?
例如,如果我要从会话状态中提取更多内容,我会引入SessionState state = session.getState().

这一切似乎都很明显,但恐怕我很难用语言表达为什么它是有道理的,不是以英语为母语的人等等。

于 2010-11-28T13:54:27.877 回答
0

可维护性和可读性是王道。幸运的是,更短通常意味着更具可读性。

这里有一些我喜欢用来切片和切块代码的技巧:

  • 变量名称:你会如何向团队中的其他人描述这个变量?你不会说“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],这不会让任何人感到困惑。

最后,我经常看到编码人员为他们的行数计数感到自豪。但重要的不是行号!我不确定实现这一点的最佳方式,但如果你对你的编码文化有任何影响,我会尝试将奖励转向那些拥有紧凑类的人:)

于 2008-09-09T09:48:31.460 回答
0

很好的解释。我认为这是一般分而治之心态的版本。

于 2008-09-09T10:25:47.377 回答