问题

你在公司项目里面看到过哪些操蛋的代码?

回答
在公司项目里见过的操蛋代码,那可真是五花八门,说起来能写一本《代码的黑暗面》了。我尽量详细地描述一些典型且令人抓狂的场景,希望能引起大家的共鸣,也希望提醒自己(以及大家)不要写出这样的代码。

以下是我印象比较深刻的几种“操蛋”代码,并附带详细描述:

1. 巨型函数/类:史诗级的“God Object”

场景描述: 某个核心业务逻辑的实现,比如用户认证、订单处理、数据同步等等,被塞进了一个几千行、甚至上万行的函数里。这个函数集所有功能于一身,处理各种异常、各种状态转换、各种输入验证、各种数据库操作、各种外部API调用,简直就是一个无所不能的“上帝对象”。
具体表现:
函数签名极其复杂: 参数多到你根本记不住,很多都是可选的,或者通过某种“魔法”传值。
内部逻辑混乱: 充斥着大量的 `ifelse ifelse` 嵌套, `switch` 语句层层叠叠,像意大利面条一样纠缠不清。
难以理解: 你需要花费数小时甚至数天来理解这个函数到底在做什么,它的每一步是如何触发的,输入什么样的参数会产生什么结果。
难以修改: 修改其中一小部分逻辑,可能会牵一发而动全身,导致其他看似无关的功能出现问题。修复一个bug可能会引入更多bug。
难以测试: 写单元测试几乎是不可能的任务,因为这个函数依赖太多外部因素,而且内部状态难以控制。
操蛋程度: 极高。这是最容易让人血压飙升的代码。每一次接触,都感觉在跟一个巨大的、脾气暴躁的怪物搏斗。

2. 魔法数字/字符串:不明所以的“暗号”

场景描述: 代码中随处可见直接使用的数字或字符串常量,但没有任何解释说明它们代表什么含义。比如:
`if (userStatus == 3) { ... }`
`return "ERR_CODE_99";`
`const timeout = 5000; // 为什么是5000?`
具体表现:
阅读困难: 你不知道 `3` 是代表“活跃用户”还是“待审核用户”?`"ERR_CODE_99"` 是什么类型的错误?`5000` 是毫秒还是秒?
维护噩梦: 当某个“魔法数字”需要修改时(比如状态码变了),你需要在整个代码库里查找所有出现过这个数字的地方,而且不确定是否遗漏了。
容易出错: 如果不小心写错了数字,比如写成了 `4` 而不是 `3`,由于没有明确的含义,错误可能不会立即暴露,直到很久以后才被发现。
操蛋程度: 中高。虽然不像巨型函数那样直接影响工作效率,但它极大地增加了代码的可读性和可维护性成本,是“代码腐烂”的常见表现。

3. 不恰当的变量/函数命名:让人生无可恋的“天书”

场景描述: 变量和函数名要么过于简短(如 `a`, `b`, `temp`, `process`),要么过于模糊(如 `handle`, `doStuff`, `processData`),要么就是与实际功能完全不符,甚至用一些缩写或者行业的“黑话”,让非该领域的人完全看不懂。
具体表现:
变量名 `a`, `b`, `c`: 尤其是在循环或者临时变量中使用,当你看到一个 `a = a + 1` 时,根本不知道 `a` 代表什么。
功能名 `process`: 这个函数是处理订单?处理用户?处理日志?谁知道呢?
意义不明的缩写: 比如 `usrMgr` (用户管理器?用户管理?),`ordProc` (订单处理?订单处理进程?)。
与实际功能相反的命名: 比如一个函数明明是用来删除数据的,却命名为 `saveData`。
操蛋程度: 中高。良好的命名是提高代码可读性的基石。糟糕的命名会让代码变成一本天书,每次阅读都需要猜测作者的意图。

4. 难以置信的异常处理(或缺乏异常处理):把“惊喜”留到最后

场景描述:
捕获了所有异常却不做任何处理: `try { ... } catch (Exception e) { }` 这种写法,就像把问题装进了黑箱里,完全不知道哪里出了错。
捕获了不应该捕获的异常: 比如捕获了 `OutOfMemoryError`,然后默默忽略。
异常处理逻辑混乱: 在 `catch` 块里进行复杂的业务逻辑,比如在这个地方进行日志记录、重试、发送邮件、回滚事务,导致代码难以理解和维护。
直接使用 `throws` 声明所有异常: 把异常处理的责任完全推给调用者,而且 `throws Exception` 是最恶心的,因为它隐藏了可能出现的具体问题。
具体表现:
问题隐藏: 当程序出现异常时,没有任何提示或日志,用户只会看到程序卡死或者出现奇怪的行为。
调试困难: 当bug出现时,你不知道是因为哪个环节出了错,需要逐层往上追溯。
掩盖了真实问题: 有时程序可能因为数据库连接失败、网络问题等导致异常,如果被 silently caught 了,就会导致数据不一致或者功能部分失效,而我们却毫无察觉。
操蛋程度: 高。异常处理是保证程序健壮性的重要环节,糟糕的异常处理直接导致程序的不可靠和难以维护。

5. 隐式类型转换/共享变量:悄无声息的“陷阱”

场景描述:
频繁的隐式类型转换: 比如在一些弱类型语言中, `1 + "2"` 可能会变成 `"12"`,或者 `0 == false`。虽然有时是为了方便,但过度使用会导致预期之外的结果。
全局变量或静态变量的滥用: 多个函数或模块通过共享同一个变量来传递信息,这种隐式的依赖关系非常容易出错。
具体表现:
难以预测的行为: 你以为你在处理数字,但实际上它已经变成了字符串。或者某个全局变量被其他地方不小心修改了,导致当前逻辑出错。
可测试性差: 很难隔离代码进行单元测试,因为测试函数的状态会受到全局变量的影响。
并发问题: 在多线程环境下,共享变量的修改如果没有加锁,极易导致数据竞争和不可预测的结果。
操蛋程度: 中。虽然有时是语言特性,但滥用和不加控制地使用,会成为难以发现的 bug 源泉。

6. 代码重复: DRY 的“叛徒”

场景描述: 在项目的不同地方,你会发现几乎完全相同的代码块被复制粘贴了多次。
具体表现:
维护成本成倍增加: 当需要修改某段逻辑时,你需要在所有粘贴的地方都进行修改,极易遗漏。
代码冗余: 增加代码量,降低代码的可读性。
容易引入不一致: 修改时可能因为疏忽,在某个地方修改了,在另一个地方忘记修改,导致逻辑不一致。
操蛋程度: 中。虽然不像巨型函数那样直接导致工作停滞,但它会不断地蚕食你的时间和精力,是代码质量下降的信号。

7. 依赖注入的缺失/混乱:紧耦合的“缠绕”

场景描述:
硬编码依赖: 一个类直接通过 `new` 来创建它的依赖对象,而不是通过构造函数或方法注入。
全局配置/单例的滥用: 各种服务都直接访问全局的配置对象或者单例实例,导致类之间存在不明确的依赖。
具体表现:
难以替换依赖: 当你需要替换某个组件时,需要修改所有使用它的地方。
难以测试: 无法轻易地 Mock 或 Stub 依赖项,使得单元测试非常困难。
可维护性差: 代码之间的耦合度太高,牵一发而动全身,修改起来非常困难。
操蛋程度: 中高。良好的依赖注入是实现高内聚、低耦合的常用手段,缺失或混乱的依赖注入会严重影响代码的可维护性和可测试性。

8. 复杂的字符串拼接/SQL 拼接:安全和可读性的“双重打击”

场景描述:
用 `+` 或 `+` `+` 来拼接大量字符串,构建 SQL 语句或者 JSON。
直接将用户输入拼接进 SQL 语句中,存在 SQL 注入的风险。
具体表现:
可读性差: 一长串的字符串拼接,难以阅读和理解。
维护困难: 修改拼接逻辑时,容易出错。
安全风险: 极易导致 SQL 注入等安全漏洞。
操蛋程度: 高。这是开发者最容易犯的低级错误之一,后果也十分严重。

写在最后:

这些“操蛋”的代码,有时候是新人经验不足写出来的,有时候是赶工期时无奈之举,有时候是团队规范缺失导致的。但无论如何,它们都会给项目的维护和发展带来巨大的阻碍。

作为开发者,我们应该时刻警惕,遵循良好的编程规范,注重代码的可读性、可维护性和可测试性。看到这样的代码,我们不能仅仅是抱怨,更应该思考如何改进,如何避免重蹈覆辙。有时候,即使是在维护别人的“操蛋”代码,也尝试着去理解、去重构,让项目变得更好。

网友意见

user avatar
       if (m_doc->isModified() == true) {     for (int i = 0; i < 100; i++)     {         save(); //Save the document for 100 times to ensure it has been saved successfully.     } }      

不知道谁写的,总之我都看哭了

user avatar

以前所在公司做激光雕刻软件,客户对雕刻速度有要求,于是乎大神把显示在客户端的所需时间乘上4/5。后来软件交给我维护,重写了界面,新版本到了客户手里雕刻速度变慢了,无论怎么优化雕刻算法,都不能达到原来的雕刻速度。

后来忘记是怎么看到那个* 4/5了,真是买了表的

user avatar

举三个我前东家的故事:

1. 写 Facebook Android的代码时候看到

       public void testSetCurrentMessageCount() {     setupGlobalTestEnvironment();     currentThread.setValue(10);     assertEquals(10, currentThread.getValue()); }  public void testGetCurrentMessageCount() {     testSetCurrentMessageCount(); // Couldn't figure out a better way... }      

对,好像是没有更好的办法来测试getter。。。

2. Facebook的Android和iOS的代码里都大量使用 Dependency Injection,主要好处是 便于测试 和 每个类实例的生命周期便于维护。只是开始的时候为了图方便,用的是类的静态方法来拿到 全局的 providerMap。后来Tech Lead规范编程风格之后,禁止大家直接调用静态的globalProvider方法,从ctor里注入。但是FB里的人图方便,还是在继续使用。最后没办法,函数名被Tech Lead改成这样:(由于保密协议,我删除了实现逻辑的代码)

       + (FBProviderMap *)globalProvidersDO_NOT_USE_OR_YOU_WILL_BE_FIRED {     // ....       FBAssertNotNil(globalProvidersDO_NOT_USE_OR_YOU_WILL_BE_FIRED, @"Call +[FBGlobalProviderMap setGlobalProvidersDO_NOT_USE_OR_YOU_WILL_BE_FIRED:] before invoking this method.");     return globalProvidersDO_NOT_USE_OR_YOU_WILL_BE_FIRED;   } }  + (FBProviderMapData *)globalProviderMapDataDO_NOT_USE_OR_YOU_WILL_BE_FIRED {   return [[self globalProvidersDO_NOT_USE_OR_YOU_WILL_BE_FIRED] providerData]; }       

2. 来自FB神童 Evan Priestley 的 Hackathon 代码 (这哥么高中毕业后开始以写程序为生,主导FB很多的大项目。离开FB之后创立:

Phabricator

)。代码本身并不是有缺陷,只是它产生了一个有趣的段子:

Tech Crunch 一直以来以爆FB的八卦为荣,但是很多时候八卦内容完全不准确,甚至是非颠倒,让高层很不爽。Evan Priestely 在一次Hackathon的时候在Facebook Photo里加了一个小按钮 “Fax Photo”,支持把当前照片传真出去,并且收费1美元(背后直接调用第三方传真公司的API)。功能本身在2009年的美国当然没鸟用,当时email,facebook和iPhone都已经高度普及,直接发照片的链接或者邮件即可,没人还想去传真一下。不过 Facebook 已具备极其强大的灰度发布功能,Evan把这个按钮发布出来,只允许 TechCrunch 公司的IP可见(除了TechCrunch,其他用户都看不到),并且在twitter上“透露”了一下Facebook Photo有新功能。

第二天,TechCrunch开始骚动了,一编辑火速发文(伴以截图)来说明Facebook Photo中“fax this”按钮的功能:TechCrunch 原文:

Facebook Now Lets You Fax Your Photos. I Have No Idea Why Anyone Would Want To Do This

。在产品流程上,还专门花了一刀成功把图片传真到了公司的传真机上。接下来,开始详细“剖析”此功能开发的初衷和适用场景,最后开始吐槽在这个互联网高度发达的时代开发这么一个古代功能并没有什么卵用。。。

过了两天,TechCrunch终于在众多网友的热心评价下,意识到自己被耍了。于是又发文补充:

Yeah Ok, So Facebook Punk'd Us

Evan Priestely 一直是Facebook很多员工心目中的天才,对,极其有争议的天才。

user avatar

我写的。

用的是某统计软件,建模的关键一步,写了个魔法数字,大概是5.28这样的。

我还贴心地注释了,这个魔法数字来自另一个同事的高端代码,请不要质疑。

正常来说要得到这个数字需要迭代优化,可能代码又要几个屏幕,加上机器跑几个小时,还不一定是最优的。

我问了另一个同事这个数字是怎么来的,他说他凭借个人经验定了一个范围,比如3-6,然后在这个范围内每隔0.01跑一一次模拟。在我码其它代码的几个小时内,他把这些模拟跑完了,发现这个数最好。

那时是半夜三点,跑完这一步后还有很多步,第二天上线。我骂了一句国骂就把这个数字写到代码里了。

第二天上线很顺利,后来做检查的时候我好心把上面那个注释加上了。加上这个注释有两个原因,一是想说有些领导根本不考虑实际情况,硬要上线。二是想说无论是多么傻逼的想法,硬要上也能有办法。

我本准备在后人问起时把这个道理告诉他们,哪知道几个月后我和那位写高端代码的同事都离职了。后来有原公司的人告诉我,直到那个项目下线,这段代码都没人碰过...有些故事,注定淹没在历史的长河里。

user avatar

我软有不少代码看上去挺好的但是会被标识成操蛋,这都归功于各种静态检查工具。


在我软代码里面起个变量名叫WhiteList都会被自动发bug,因为用White来表示允许就属于种族歧视,一定要改成AllowList。我会告诉你我们因此还真的改了服务接口而且还要做向下兼容吗?


还有变量名叫country也会被查,因为正确的说法是country and region。


我不是说public的类成员哦,private的都不行。


另外kill,dead之类的字眼都是有问题的。


当然你真的一定要用这些词你就得说明理由,然后加supression。


要像 @vczh 那样变量起名bitch什么的那是要死一百遍啊。


所以你现在知道写点代码有多难了吧。

user avatar

1. 时空双O(n2)字典查表法

运输管理公司,公司手底下多家分公司,各个分公司都有自己描述线路状态的代码,而总公司有另外一套。所以在总公司的屏幕上,需要用一套“字典”,把各个分公司的描述“翻译”成总公司的语言。

简单吧?

稍微做过leetcode的都知道hashmap搞个单次翻译O(1)时间复杂度简直基本操作。

实际业务中不一定有hashmap这么简单,弄个array挨个找,也至少是单次O(n)复杂度对吧?

很简单吧?how wrong could that be?

就有contractor给我写了个时间/空间复杂度双O(n2)的代码出来。

人家翻译之前,先把字典重新整理一遍,挨个来,整理的过程是先把每个词提取出来,在处理每个词的过程中,都要遍历一遍整个字典,行了妥妥时间复杂度O(n2)。然后还没完,最终导出的字典每个词后边都缀了相当于原来整本字典的数据(非shallow copy),得,空间也O(n2)了。

你以为就完了?没,人家每翻译一个词都要重新干一遍上边的事。

我说大哥你真行,你这相当于拿着汉英双译词典看英文小说,每查一个词之前,都要把汉英双译词典里边每个词都抄一份出来抄成英汉双译词典,别人印书洛阳纸贵,你看书就够了,你这辈子能看完半本就算我输。

2. 迷人的Class

说完了上边的再说接下来这个,公司的所谓Typescript项目。一天debug,发现一段代码如下:

const user = new User();

哦原来是新建一个用户,不过奇怪了,为什么要在后端程序里新建一个用户?用户不应该是从jwt token,DB或者相应服务里拉取的吗?接着看看怎么用的:

const email = user.getEmail(req.user);

慢着慢着,这个req.user又和这个user有什么关系?还有为什么这个user获得Email居然要另外一个user的信息?难道这个user是被另外一个user管理的吗?我还是看看这User的class definition吧:

import User from '../user-helper';

啥玩意?这个是个helper?我进文件看看:

       export default class UserHelper {   getEmail(user) {     return user.email;   } }      

和大多数回答会简化code不同,我这个就是源代码,一刀未剪,我看到后一度半分钟没说出话来,槽太多不知道怎么吐:

  1. 就一个function的事需要写一整个class吗?
  2. 好吧,一个helper class,那这method有必要做成class method吗?你又没有dependency injection,写成static的不行吗?
  3. 好吧,你非要class method,那你import的时候能别重命名吗?
  4. 而且有必要吗?你读个property就得写个helper method?那调用你这个helper是不是还得再写一个helper?

看完这个我就叫我经理过来,说:

I'm not sure if someone gets paid by the number of lines committed. If yes, I want his contract as well.

user avatar
“你把距离显示优化一下,超过1000米就显示公里,精确到小数点后2位。”
“好的!”

然后,在Code Review的时候,我看到了如下的代码片段:

       NSString *strLog = [[NSString alloc]init];  NSString *distance = [strLog stringByAppendingFormat:@"%d",(int)dis]; if (distance.length >=4) {     int q = 2;     for (int i=0; i<distance.length; i++) {         if (i >4) {             q+=1;         }     }     NSMutableString *str = [NSMutableString stringWithString:[distance substringToIndex:q]];     [str insertString:@"." atIndex:1];     if (str.length >4) {         NSString *string =  [str substringToIndex:4];         return [NSString stringWithFormat:@"%@公里",string];              }     return [NSString stringWithFormat:@"%@公里",str];      }else{     return [NSString stringWithFormat:@"%@米",distance]; }      

最后,我饱含热泪,心怀不忍地将他辛勤不易的工作成果改成了如下:

       if (dis<1000) {     return [NSString stringWithFormat:@"%.0f米",dis]; } return [NSString stringWithFormat:@"%.2f公里",dis/1000.0];      
user avatar
       (a != b) ? b : a      

以前在人人上贴了这段代码来吐槽前公司的代码质量,被转发后火了,因为神最右的注解:

和老婆意见不一致的时候听她的;意见一致的时候听我的

========================

关于评论中提到的运算符重载等问题,这里统一回答一下:

我摘的这段代码被我省略了上下文。实际情况是,变量是基本类型,也没有重载运算符等其他让这段代码有意义的原因。我刚看到它的时候也绞尽脑汁去想有没有别的可能,试图理解这么写的用意,最后发现只是代码写得烂而已。

user avatar
       enum FiveLine {   Gold,   Wood,   Water,   Fire,   Earth, };      

看枚举名字不知道五行(hang)是什么鬼,看了枚举内容恍然大雾,原来是五行(xing)……

user avatar

哎。。。跟你们说个奇葩的事情吧。。。

背景:做手持设备的公司,你们可以理解成手机一样的东西。

第一家公司有个大神。写驱动的。

不知道怎么写并发的代码。。。

如果出现问题,就直接sleep。并且创造了自己的一套调试理论。

于是在代码里面就会看到各种莫名其妙的sleep(xxx)。

而且很多都还是神奇数字。不按那个休眠,并发就会出问题...

由于大神杰出的工作,让代码出问题的概率降到了8小时一次。

又特么由于猪队友的硬件设计,我们那里的机器电池在连续使用6小时之后就没电了,于是客户相当于每6小时就要重启一次机器...

嗯...于是,相安无事。

==============================

没想到几句吐槽这么多赞。

看到评论区里面真的有人这么写代码,又想写两句。

借用

@vczh

说的一句话(原话记不太清)

现在硬件性能逐步提升,写出能跑的程序太简单,而写出真正好的程序太难。

希望大家在写程序上多追求极致和规范。

如果碰到看起来很傻的问题,先多看看别人怎么解决的。

别什么事情都想当然。

上面的话说给大家,也更多的说给我自己。

user avatar

最常见的大概就是

if (b == true) {...}

我不常写c,不知道c程序员是不是觉得这种写法是理所当然的,但当我在java代码中频繁的看到这种代码的时候,我真的很无力。。。

————————————————————

补充一个朋友跟我讲过的笑话,让大家看看中国程序员在日本造了什么孽。

那个朋友有段时间短期做了两个月某大公司的运营系统的维护工作,然后告诉我那个项目的代码中充斥让人瞠目结舌的变量名。那些变量名有多变态呢,我来慢慢说。

比如我们如果有个订单管理的子模块,我们需要定义一个叫做「订单」的类,一般来说,就用英语order就可以了。

Order order = new Order();

大家都知道,某些英语不灵光的二杆子程序员呢,有时会用拼音:

Dingdan dingdan = new Dingdan();

老实说,这个虽然看起来有点恶心,但至少人民群众都看得懂是吧,也不算太糟糕。日本的程序员其实大多英语也不灵光, 日语的订单一般称为「注文票」,好了,日本的二杆子程序员呢其实也经常写下面的代码:

Tyumonhyo tyumonhyo = new Tyumonhyo();

跟我大天朝的拼音也算是异曲同工了。

那么,那个项目的代码牛x在哪儿呢,或者说变态在哪儿呢,我们都知道,有很多天朝程序员在日本混饭吃,很多系统都是中国程序员开发的,于是呢,我们就看到了下面的代码:

Zhuwenpiao zhuwenpiao = new Zhuwenpiao();

Bingo!!!你们知道吗,这个程序员为我天朝在日程序员作出了多大的贡献?这种变量名,只有我大天朝混日本饭的人才看得懂,只有我们能维护啊,这是怎样的一种机智,难道不值得大家鼓掌吗?

(再补充一点,就是这种变量风格不是个别代码,而是整个项目全部是这种风格,应该是某个外包公司团伙作案。。。)

user avatar
                #define TRUE FALSE             

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有