it-swarm.cn

查看代码时,哪些事情会立即敲响警钟?

几周前,我参加了一次软件工艺活动,当时发表的评论之一是“我相信我们在看到错误代码后都会认出错误代码”,每个人都明智地点头,无需进一步讨论。

这类事情总是让我感到担忧,因为每个人都认为自己是高于平均水平的司机。尽管我认为我可以识别错误的代码,但我还是想了解更多有关其他人认为代码气味的信息,因为很少有人在博客上或仅在少数书籍中对此进行详细讨论。尤其是,我想听到一种语言而不是另一种语言中的代码味道会很有趣。

我将从一个简单的开始:

源代码管理中注释掉代码比例很高的代码-为什么会出现?是要删除吗?是半成品吗?也许不应该将其注释掉,而应该仅在有人测试某些东西时才完成?就我个人而言,即使只是在这里和那里的奇数行,我也觉得这很烦人,但是当您看到大块散布在其余代码中时,那是完全不可接受的。通常,这也表明该代码的其余部分也可能具有可疑的质量。

98
FinnNk
/* Fuck this error */

通常在废话try..catch块中找到,它往往引起我的注意。与/* Not sure what this does, but removing it breaks the build */差不多。

还有两件事:

  • 多个嵌套的复杂if语句
  • 尝试捕获块,用于定期确定逻辑流
  • 具有通用名称processdatachangereworkmodify的函数
  • 100行中六到七种不同的支撑样式

我刚发现的一个:

/* Stupid database */
$conn = null;
while(!$conn) {
    $conn = mysql_connect("localhost", "root", "[pass removed]");
}
/* Finally! */
echo("Connected successfully.");

正确,因为必须蛮力地使用MySQL连接才是正确的处理方式。事实证明,数据库的连接数存在问题,因此它们一直处于超时状态。他们没有调试它,而是反复尝试直到它起作用为止。

128
Josh K

对我而言,主要的危险标志是重复的代码块,因为这表明该人要么不了解编程基础知识,要么就因为太害怕而无法对现有代码库进行适当的更改。

我以前也把缺少注释视为危险信号,但是最近在处理很多非常好的代码时没有注释,我对此有所缓解。

104
Ben Hughes

尽管没有增加任何实际价值的事实,仍然试图证明程序员是多么聪明的代码:

x ^= y ^= x ^= y;
74
Rei Miyasaka
  • 20,000(夸张)线功能。任何占用多个屏幕的功能都需要重构。

  • 同样,类文件似乎永远存在。可能有一些概念可以抽象为类,这些概念可以弄清原始类的目的和功能,以及可能在哪里使用它,除非它们都是内部方法。

  • 非描述性,非平凡变量,或太多非琐碎的非描述性变量。这些使人们推论出实际发生的谜团。

62
Dominique McDonnell
{ Let it Leak, people have good enough computers to cope these days }

更糟糕的是,这是来自商业图书馆!

61
Reallyethical

注释非常冗长,以至于如果有英文编译器,它将可以编译并完美运行,但不会描述代码没有的任何内容。

//Copy the value of y to temp.
temp = y;
//Copy the value of x to y.
y = x;
//Copy the value of temp to x.
x = temp;

此外,如果代码遵循一些基本准则,则可以删除对代码的注释:

//Set the age of the user to 13.
a = 13;
53
Rei Miyasaka

编译时产生警告的代码。

42
Rei Miyasaka

名称中带有数字而不是描述性名称的函数,例如:

void doSomething()
{
}

void doSomething2()
{
}

请让函数名称有意义!如果doSomething和doSomething2做类似的事情,请使用区分差异的函数名称。如果doSomething2是doSomething的功能突破,请为其功能命名。

36
Wonko the Sane

魔术数字 或魔术字符串。

   if (accountBalance>200) { sendInvoice(...); }

   salesPrice *= 0.9;   //apply discount    

   if (invoiceStatus=="Overdue") { reportToCreditAgency(); }
36
JohnFx
  • 也许不是最坏的情况,但可以清楚地显示实现者级别:

    if(something == true) 
    
  • 如果一种语言具有for循环或迭代器构造,则使用while循环还可以演示实现者对该语言的理解程度:

    count = 0; 
    while(count < items.size()){
       do stuff
       count ++; 
    }
    
    for(i = 0; i < items.size(); i++){
      do stuff 
    }
    //Sure this is not terrible but use the language the way it was meant to be used.
    
  • 文档/注释中的拼写/语法不佳几乎和代码本身一样吃。这样做的原因是因为代码是供人类阅读和运行机器的。这就是为什么我们使用高级语言的原因,如果您的文档难以理解,则会使我在不看代码的情况下先发制人地对代码库形成负面意见。

36
Chris

我立即注意到的是深层嵌套的代码块(如果是,则是等)的频率。如果代码经常深入超过两个或三个级别,则表明存在设计/逻辑问题。而且,如果它深入到8个巢穴,则最好有充分的理由使它不被分解。

29
GrandmasterB

给学生的课程评分时,有时可以说出“眨眼”的时刻。这些是即时线索:

  • 格式不良或不一致
  • 连续超过两个空行
  • 非标准或不一致的命名约定
  • 重复代码,重复次数越多,警告越强烈
  • 一段简单的代码应该过于复杂(例如,以复杂的方式检查传递给main的参数)

我的第一印象很少是错误的,这些警告钟大约是95%的时间。一个例外是,该语言的新学员使用的是另一种编程语言的样式。挖掘并用另一种语言重新阅读他们的风格,这为我消除了警钟,然后学生获得了充分的信誉。但是这种例外很少见。

在考虑更高级的代码时,这是我的其他警告:

  • manyJava仅是用于保存数据的“结构”的类。字段是公共的或私有的,并且使用获取/设置程序,但这仍然不是经过深思熟虑的设计的一部分。
  • 名称不佳的类,例如只是一个名称空间,并且代码中没有真正的凝聚力
  • 引用代码中甚至没有使用的设计模式
  • 空的异常处理程序,无需解释
  • 当我在Eclipse中提取代码时,代码中有数百个黄色的“警告”,主要是由于未使用的导入或变量

在样式方面,我通常不喜欢看到:

  • 仅回显代码的Javadoc注释

这些是仅线索不良代码。有时看似不好的代码实际上不是,因为您不了解程序员的意图。例如,可能有一个很好的理由,即某些事情看起来过于复杂-可能还有其他考虑因素在起作用。

28
Macneil

个人最喜欢的/宠爱的人:IDE生成的名字被断断续续。如果TextBox1是系统中的主要变量,那么重要的是代码审查。

25
Wyatt Barnett

完全未使用的变量,尤其是当变量的名称类似于所使用的变量名称时。

25
C. Ross

很多人提到:

//TODO: [something not implemented]

虽然我希望这些东西得到实施,但至少他们做了一个说明。我认为更糟糕的是:

//TODO: [something that is already implemented]

如果您从未想过将它们删除,那么Todo就是毫无价值和令人困惑的!

21
Morgan Herlocker

方法名称中的连词:

public void addEmployeeAndUpdatePayrate(...) 


public int getSalaryOrHourlyPay(int Employee) ....

澄清:敲响警钟的原因是它表明该方法可能违反了 单一责任原则

20
JohnFx

需要我向下滚动才能阅读所有内容的方法。

20
BradB

显然将GPL的源代码链接到一个商业的,封闭源代码的程序中。

它不仅造成了直接的法律问题,而且根据我的经验,它通常表示粗心或无关紧要,这也反映在代码的其他地方。

13
Bob Murphy

不可知的语言:

  • _TODO: not implemented_
  • int function(...) { return -1; }(与“未实现”相同)
  • 由于非异常原因而引发异常。
  • 错误使用或不一致使用_0_,_-1_或null作为例外返回值。
  • 断言没有令人信服的评论说为什么它永远不会失败。

特定于语言(C++):

  • 小写的C++宏。
  • 静态或全局C++变量。
  • 未初始化或未使用的变量。
  • 任何显然不是RAII安全的_array new_。
  • 显然不是边界安全的数组或指针的任何使用。这包括printf
  • 数组未初始化部分的任何使用。

特定于Microsoft C++:

  • 与任何Microsoft SDK头文件已定义的宏冲突的任何标识符名称。
  • 通常,对Win32 API的任何使用都会引起很大的警钟。始终打开MSDN,并在有疑问时查找参数/返回值定义。 (编辑)

特定于C++/OOP:

  • 实现(具体类)继承,其中父类同时具有虚拟和非虚拟方法,而在应该/不应该虚拟之间没有明显的概念区别。
9
rwong

使用大量文本块,而不是枚举或全局定义的变量。

不好:

if (itemType == "Student") { ... }

更好:

private enum itemTypeEnum {
  Student,
  Teacher
}
if (itemType == itemTypeEnum.Student.ToString()) { ... }

最好:

private itemTypeEnum itemType;
...
if (itemType == itemTypeEnum.Student) { ... }
8
Yaakov Ellis

Bizarre缩进样式。

有两种非常流行的风格,人们会将这场辩论带到坟墓。但是有时候我看到有人使用一种非常罕见的,甚至是一种自家的缩进样式。这表明他们可能没有与自己以外的任何人进行编码。

8
Ken

弱类型的参数或方法的返回值。

public DataTable GetEmployees() {...}

public DateTime getHireDate(DataTable EmployeeInfo) {...}

public void FireEmployee(Object EmployeeObjectOrEmployeeID) {...}
8
JohnFx

代码气味:不遵循最佳做法

这类事情总是让我感到担忧,因为每个人都认为自己是高于平均水平的司机。

这是您的新闻快讯:世界上50%的人口智力水平低于平均水平。好的,所以有些人的智力完全是平均的,但是我们不要挑剔。另外,愚蠢的副作用之一是您无法识别自己的愚蠢!如果结合使用这些语句,事情看起来就不会那么好。

查看代码时,哪些事情会立即敲响警钟?

提到了很多好东西,并且一般来说,不遵循最佳实践似乎是一种代码味道。

最佳做法通常不是随机发明的,并且经常是有原因的。很多时候它可能是主观的,但以我的经验,它们大多数都是合理的。遵循最佳做法应该不是问题,如果您想知道为什么会这样,请研究它而不是无视和/或抱怨它-也许是有道理的,也许不是。

最佳做法的一个示例可能是在每个if块中使用curly,即使它仅包含一行:

if (something) {
    // whatever
}

您可能没有必要,但我最近 read 是错误的主要来源。 Stack Overflow 上也讨论了始终使用方括号,并且检查if语句是否具有方括号也是 rule in [〜#〜] pmd [〜#〜] /,这是Java的静态代码分析器。

请记住:“因为这是最佳做法”,对于“您为什么要这样做?”这个问题永远是无法接受的答案。如果您无法阐明为什么某事是最佳实践,那么它不是最佳实践,那是一种迷信。

7
Vetle
  • 多个三元运算符串在一起,因此它不像_if...else_块,而是变成了_if...else if...[...]...else_块
  • 长变量名,不带下划线或驼峰。我已经提出的一些代码示例:_$lesseeloginaccountservice_
  • 文件中的几百行代码,几乎没有注释,而且代码不是很明显
  • if语句过于复杂。代码示例:if (!($lessee_obj instanceof Lessee && $lessee_obj != NULL))我砍成if ($lessee_obj == null)
7
Tarka

捕获一般异常:

try {

 ...

} catch {
}

要么

try {

 ...

} catch (Exception ex) {
}

区域过度使用

通常,使用太多区域向我表明您的课程太大。这是一个警告标志,表示我应该进一步研究该代码段。

6
Erik van Brakel

谁能想到一个示例,其中代码应通过绝对路径合法地引用文件?

6
Rei Miyasaka

说“这是因为froz子系统的设计完全令人厌烦”。

这贯穿了整个段落。

他们解释说,需要进行以下重构。

但是没有做到。

现在,由于时间或能力方面的问题,他们可能当时被老板告知他们不能更改它,但这也许是因为人们太小了。

如果主管认为是j.random。程序员无法进行重构,那么主管应该这样做。

无论如何,我知道代码是由一支分散的团队编写的,具有可能的权力策略,并且他们没有重构沉闷的子系统设计。

真实的故事。它可能发生在您身上。

6
Tim Williscroft
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...

当然没有任何类型的文档,并且偶尔会嵌套#defines

5
Sven

具有显式delete语句的C++代码(除非我正在研究智能指针实现的内在特性)。 'delete'是内存管理的'goto' [〜#〜] imho [〜#〜]

与此紧密相关的代码如下:

// Caller must take ownership
Thing* Foo::Bar()

(如果有评论,请给自己算幸运!)。并非智能指针是火箭科学。 std::auto_ptr是用于这种事情的(记录强制执行注释中表达的意图),并且是 standard 的一部分=很久以前了。

这些尖叫着不受欢迎的旧代码,或者在90年代初某个时候陷入僵局的维护者。

5
timday

重新实现语言基本功能的功能。例如,如果您在JavaScript中看到了“ getStringLength()”方法,而不是对字符串的“ .length”属性的调用,则说明您遇到了麻烦。

5
Ant

类命名约定表明对他们试图创建的抽象的理解不足。或根本不定义抽象。

在一个VB)类中想到了一个极端的例子,我曾经见过一个标题为Data并且长30,000+行的...在第一个文件。这是一个局部类,至少分成六个其他文件。大多数方法是包装存储过程的包装,其名称为FindXByYWithZ()

即使没有那么生动的例子,我也确信我们所有人都只是将逻辑“倾销”到了一个构思欠佳的类中,给了它一个完全通用的标题,后来又后悔了。

5
Bryan M.
ON ERROR RESUME NEXT

对于大多数ASP.NET开发人员来说,必须维护Classic ASP)应用程序是很必要的,但是打开一个通用的include文件并看到第一行令人头疼。

4
richeym

当没有注释或文档时,无论代码做什么或应该做什么(即“代码就是文档”)。

带数字后缀的方法或变量(例如Login2())。

4
leson
try
{
//do something
}
catch{}
3
Tom Squires

逻辑上无法输入执行路径的代码。

var product = repository.FindProduct(id);

log.Info("Found product " + product.Name);

if (product != null)
{
    // This will always happen
}
else
{
    // **This won't ever happen**
}

要么

if (messages.Count > 0)
{
    // Do stuff with messages
}
else if (messages.Count == 1)
{
    // **I hope this code isn't important...**
}
3
rmac
  • 将每个局部变量放入方法块的第一行。特别是与长方法结合使用。
  • 使用布尔变量打破循环/跳过迭代,而不仅仅是使用break/continue
3
Oliver Weiler

从以Java为中心的角度来看:

  • 非标准编码风格。
  • 非私有变量。
  • 字段上缺少final
  • 毫无意义或过度使用继承。
  • 巨大的类或代码块。
  • 评论太多(反正它们只是一厢情愿)。
  • 非结构化日志记录。
  • 获取器和设置器(接口应与行为有关)。
  • 重复的数据。
  • 奇怪的依赖关系。
  • 静态变量,包括线程全局变量。
  • 对于多线程代码,同一类的预期在不同线程中运行的部分(尤其是GUI代码)。
  • 无效代码。
  • 字符串操作与其他代码混合在一起。
  • 通常,混合层(高级的东西,再加上遍历一组原语或线程处理)。
  • 任何使用反射。
  • catch块没有有用的代码(错误:注释,printf,日志记录或仅是空的)。
3
Tom Hawtin - tackline

在用户界面(例如,文本框)中使用隐藏对象来存储值,而不是定义适当范围和类型的变量。

2
MartW

每当我阅读以下内容时:

//Don't put in negative numbers or it will crash the program.

或类似的东西。如果是这种情况,请放一个断言!让调试器知道在运行时您不希望这些值,并确保代码与调用者清楚地说明了合同。

2
wheaties

此类代码:

        if (pflayerdef.DefinitionExpression == "NM_FIELD = '  '" || One.Two.nmEA == "" || 
            One.Two.nmEA == " " || One.Two.nmEA == null ||
            One.Two.nmEA == "  ")
        {                
            MessageBox.Show("BAD CODE");
            return;
        }

这是来自真实的实时生产代码库!

2
George Silva

至于魔术数字:如果在不同的地方使用它们会很不好,并且更改它需要您在多个地方进行同步。但是,在一个位置上的一个数字并不比用一个常数表示仍在一个位置上使用的一个数字更糟糕。

此外,常量在您的应用程序中可能没有太多位置。在许多数据库应用程序中,这些内容应按照应用程序或用户设置存储在数据库中。为了完成其实现,他们涉及到此设置,在ui中的位置以及最终用户文档中的注释……所有这些都是一种过度设计,并且如果每个人在5时都非常满意,那么这就是浪费资源(和5。)

我认为您可以保留数字和字符串,直到需要在该位置之外使用此数字为止。然后是时候将其重构为更灵活的设计了。

至于字符串...我知道参数,但这是另一个地方,没有必要进行一字符串到一常数转换。尤其是如果字符串仍然来自恒定的实现(例如,从外部应用程序生成的导入,并且具有简短且可识别的状态字符串,就像“过期”一样)。无论如何仅将“过期”转换为STATUS_OVERDUE时,有很多意义。

我非常不增加复杂性,如果它实际上并未在灵活性,重用性或错误检查方面创造出所需的好处。当您需要灵活性时,请对其进行正确编码(重构很重要)。但是如果不需要...

2
Inca

紧密耦合的代码。尤其是当您在代码中间看到许多硬编码的内容(网络打印机的名称,IP地址等)时。这些应该在配置文件中甚至是常量中,但是以下内容最终会导致问题:

if (Host_ip == '192.168.1.5'){
   printer = '192.168.1.123';
} else
  printer = 'prntrBob';

有一天,鲍勃将辞职,他的打印机将被重命名。有一天,打印机将获得一个新的IP地址。某天192.168.1.5将要在Bob的打印机上打印。

我最喜欢的口头禅:总是像杀人的精神变态者那样写代码,他知道你的住所必须维护!

2
davidhaskins

表明程序员几年前从未适应Java 5的代码:

  • 使用迭代器,而不是“为每个”
  • 不在集合中使用泛型,并将检索到的对象强制转换为期望的类型
  • 使用Vector和Hashtable等古代类

不知道现代的多线程方式。

2
Dave Briccetti

对于SQL:

第一个重要指标是隐式联接的使用。

接下来是对tableB的左联接与WHERE子句结合使用,例如:

WHERE TableB.myField = 'test'

如果您不知道这样做会带来不正确的结果,那么我不相信您编写的任何查询都会给出正确的结果。

2
HLGEM

我们的旧版VB6代码可以打开任何模块或表单代码页面,并找到一个充满公共或全局#&@ !变量在顶部声明,并在@&!各处引用的变量的屏幕。 (*!#程序。ARGH!!!!

(哇,我不得不把它弄出来:-))

2
HardCode

像这样

x.y.z.a[b].c

这闻起来像生物危害。如此多的成员引用从来都不是一个好兆头,是的,这是我正在使用的代码中的典型表达。

2
Gaurav

任何像这样的东西

// TODO: anything could be here!

编辑:我应该限定我在生产代码中的意思。但是,即使在致力于源代码控制的代码中,这也不是一件好事。但是,那可能是个人的事情,因为我想结束一天的工作,绑紧我所有的松懈结局:)

编辑2:当我在已建立的代码中看到此内容时,我应该进一步证明我的意思。就像已经存在多年的东西了,我正在修复它。我看到一个待办事项,那是闹钟响起的时候。 TODO(对我而言)暗示代码由于某种原因从未完成。

2
Antony Scott

在Java中使用synchronized关键字。

正确使用synchronized并不意味着有什么问题。但是在我使用的代码中,似乎每次有人尝试编写线程安全代码时,他们都会出错。所以我知道,如果我看到此关键字,则必须对代码的其余部分格外小心...

1
Guillaume

可以通过更好的结构优化代码的窥孔优化,例如当在普通C/C++/Java/C#中进行二进制搜索将是适当的(并且更快)时,将在内联汇编中实现线性搜索。

这个人要么缺少一些基本概念,要么没有优先感。后者更加令人担忧。

1
Rei Miyasaka

@FinnNk,关于代码注释,我同意。真正困扰我的是这样的事情:

for (var i = 0; i < num; i++) {
    //alert(i);
}

或用于测试或调试的所有内容,然后注释掉然后提交。如果只是偶然的话,那没什么大不了的,但是如果到处都是,它会弄乱代码,使您很难看清发生了什么。

1
Elias Zamaria
  • $ data -就像广告“物理对象,现在每5个中的价格低了100个!”
  • 代码中的TODO项-使用错误/故障单/问题跟踪器,使人们知道在产品级别而不是文件级别需要什么。
  • 工作登录代码-这就是版本控制的用途。
1
l0b0

任何违反重要原则的行为。例如,已经提出了一些反模式(不可思议的数字-参见 http://en.wikipedia.org/wiki/Anti-pattern )。反模式之所以被称为是因为它们会引起问题(也已经提到过-脆弱性,维护噩梦等)。另外,请注意是否违反了SOLID原则-请参阅 http://en.wikipedia.org/wiki/Solid_(object-oriented_design) )另外,不要考虑分层(UI内的数据访问等),拥有编码标准和代码审查有助于解决这一问题。

1
Tim Claason

其中大多数来自Java经验:

  • 字符串输入。就是不行。
  • 类型转换通常会指向现代Java中的代码味道。
  • 口袋妖怪例外反模式(当您必须全部抓住它们时)。
  • 货物崇拜尝试在不合适的地方进行功能编程。
  • 在适当的地方不要使用类似函数的构造(Callable,Function等)。
  • 无法利用多态性。
1
Ben Hardy

当代码看起来一团糟时:带有“待办事项”和“自我注释”的注释以及kes脚的笑话。显然在某些时候仅用于调试目的的代码,但是后来并未删除。名称表明作者不认为以后会有人阅读的变量或函数。这些名称通常会很长且很笨拙。

另外:如果代码缺少节奏。长度的功能大相径庭。不遵循相同命名方案的功能。

略有关系:如果程序员手写的技巧过硬,这总是让我感到紧张。或者他们是不好的作家还是不好的传播者。

1
KaptajnKold

我曾经在一个项目中工作,承包商已对所有标准数据类型(从int到字符串)进行tyepdef定义,包括指向晦涩名称的指针。他的方法使理解代码变得非常困难。警告我的另一种风格是过早的灵活性。我曾经使用过的产品在DLL中的完整代码以不可预测的顺序加载。所有这些适应未来的发展。一些DLL使用线程包装器来实现可移植性。通过读取源代码来调试程序或预测流程是一场噩梦。定义分散在头文件中。毫不奇怪,该代码无法在第二个版本中继续存在。

1
VKs

有时,我看到方法的各个部分都给出了所有可能的输入,但它永远不会运行,因此一开始就不应该让人们感到困惑。一个例子是...
如果只能在Admin超级用户的上下文中调用该方法,并且我看到一些检查请求用户是否不是Admin超级用户...:/

1
chiurox

表示缺乏克制的不满评论:

//I CAN'T GET THIS F^#*&^ING PIECE OF S$^* TO COMPILE ON M$VC

他们要么脾气暴躁,要么没有足够的经验来了解编程中不可避免的挫折。

我不想和那样的人一起工作。

1
Rei Miyasaka

与其他人列出的东西相比,这是一个轻微的症状,但是我是一个Python程序员,我经常在我们的代码库中看到这一点:

try:
    do_something()
except:
    pass

通常这会向我发出信号,表明程序员并不真正知道(或关心)为什么do_something可能会失败(或失败)–他只是一直“摆弄”直到代码不再崩溃。


对于那些来自Java风格的背景的人,该块是Python

try {
    doSomething();
} catch (Exception e) {
    // Don't do anything. Don't even log the error.
}

事情是,Python对“非异常”代码使用异常,例如键盘中断或for循环中断。

1
mipadi

到处都是吸气剂,让我很害怕。

还有一件特别的事情:吸气剂委托给其他吸气剂。

这很不好,因为这意味着编写该文件的人不了解面向对象的基础(即封装),这意味着数据在哪里,作用于该数据的方法应该在哪里。

委派是一种方法,而不是所有获取方法。原则是“告诉,不要问”;告诉一件事情一件事情要做,不要要求一千件事情然后自己去做。

吸气剂使我震惊,因为这意味着其他oop原则将被违反。

0
Belun

缺少类型信息。

看一下这些方法签名:

  1. public List<Invoice> GetInvoices(Customer c, Date d1, Date d2)

  2. public GetInvoices(c, d1, d2)

在(1)中有明确性。您确切知道调用该函数所需的参数,并且很清楚该函数返回什么。

在(2)中只有不确定性。您不知道要使用什么参数,也不知道如果有什么函数会返回什么。您实际上被迫使用效率低下的反复试验方法进行编程。

0
ThomasX