为什么程序员不喜欢写注释?

news2024/11/23 15:52:19

现在的项目开发里,代码注释就像程序员的头发,越来越少。

尤其是国内,这种现象不仅是在小公司小团队中司空见惯,就算在大公司,以及大团队中的开源项目里,也是屡见不鲜。

上图是我在阿里的 Druid 项目源码里截的。DruidDataSource 是 Druid 重度使用的核心类,非常关键,可是哪怕这种关键的核心类,也见不到什么注释。

没有注释对我们读代码带来了很多的不便之处。就像扔给你一个数码产品,上面堆叠着密密麻麻的功能按键,但是却没有给你说明书。

那为什么代码注释消失了呢?

我尝试总结一下原因:

1. 国内程序员的职业环境对加注释不友好

在国内这种环境里,程序员们每天在苦闷的 996 中挣扎,各种大活小活不断地做着,正常写代码都忙得不可开交,加注释更是进一步提升了工作量,没人喜欢自己给自己加工作量的。

咱们想想,在费劲巴拉地写完一大堆代码之后,经过反复自测修改之后,好不容易调通了,脑子已经晕乎乎的了,你此时会有多大心思去写这段注释呢?

又再想想,你可能想着要给代码加注释呢,突然这边产品拉你开会,又或者那边运营告诉你,需求变了,刚写好的代码还得再改改……此时,你还有给代码加注释的念头吗?

另外,注释这事儿,写好了是很费精力的。一般来说,一段好的注释,要能在有限的行数之内说明出:被它注释的代码到底做了什么,是个怎样的概念以及为什么会写这段代码。

写注释麻烦不说,关键是注释还不算咱们程序员的工作量。

程序员的工作是把业务用程序实现,工作结果里不看你注释了多少代码,也不看你注释写的好还是坏,只看你的程序是不是写完了,满足了需求没有,会不会上线出什么问题。

至于注释,它滚出了程序员兄弟们的 KPI。有多少公司能像 Google 那样去 Review 代码的?BAT 有一个算一个,都差点意思。

所以,国内程序员们糟糕的环境,是代码注释少的首要原因。

2. 看待注释的方式出现了变化

Java 是一门面向对象的语言,从它出世以来,业界就不断地为 Java 制定了数不清的规范。

在 2008 年,集这些规范于大成的《Clean Code》—— 中文名叫《代码整洁之道》这本书出现了。

在《代码整洁之道》中有个理念就是,注释是为了弥补代码表达能力不足的一种不得已的做法。如果代码能表达清楚,那就没必要写注释。

甚至,这本书的作者认为写注释都需要用 failure 这个词来形容,也就是说,如果你写了注释,那就说明你的代码不够好,你写好代码的努力失败了。

这个理念在业界也被不少大牛们认可了。所以,后面就有越来越多的人认为:代码写的够好,就不用写注释了。

如果大家有空,可以去看看《代码整洁之道》的第四章,里面详细说明了这种如今被业界不少人接受的关于注释的理念。

所以,“好代码不需要注释”这种观点也是造成注释少的一个原因。

3. 注释没有规范,导致质量参差不齐

很多团队里,是没有注释规范的。对怎么注释,在哪里注释没有任何规定,随意程序员们自由发挥。

这就麻烦了,注释一旦写了,它就很关键了。因为

错误的注释,比没写注释还祸害人。

注释写的很差,那不仅没起到注释本应该起到帮助读代码的作用,反而还可能影响读代码,甚至还能把人带坑里。

如果没有注释规范,往往经常就会出现有人做的好有人做的差的情况。

比如,有人到处加注释,i = i + 1; //把i加1连这种简单代码都恨不得加注释,这就有点脱裤子放屁了。

还有的人写注释了,但是需求变了,代码改了之后,注释懒得改了。又或者是改代码的人不是原作者,新人改完之后压根就没意识到要改注释。

所以,如果没有规范,很多程序员对注释没有什么正确的概念,没写好注释由此还引来了埋怨……久而久之,就没人爱干加注释这件事了。

到底应该怎么写注释呢?

谈了那么多不写注释的原因,这里也想说明一下我对注释的观点。

我个人并不怎么赞同《代码整洁之道》对注释的观点,我自己读有好注释的代码,直接就省了五成以上的力气。有好注释的代码读起来,就像常年脑血栓,一朝被皮搋子打通了一样,那叫一个顺畅。

比如,看看 Netty 的注释:

/**
 * A nexus to a network socket or a component which is capable of I/O
 * operations such as read, write, connect, and bind.
 * <p>
 * A channel provides a user:
 * <ul>
 * <li>the current state of the channel (e.g. is it open? is it connected?),</li>
 * <li>the {@linkplain ChannelConfig configuration parameters} of the channel (e.g. receive buffer size),</li>
 * <li>the I/O operations that the channel supports (e.g. read, write, connect, and bind), and</li>
 * <li>the {@link ChannelPipeline} which handles all I/O events and requests
 *     associated with the channel.</li>
 * </ul>
 *
 * <h3>All I/O operations are asynchronous.</h3>
 * <p>
 * All I/O operations in Netty are asynchronous.  It means any I/O calls will
 * return immediately with no guarantee that the requested I/O operation has
 * been completed at the end of the call.  Instead, you will be returned with
 * a {@link ChannelFuture} instance which will notify you when the requested I/O
 * operation has succeeded, failed, or canceled.
 *
 * <h3>Channels are hierarchical</h3>
 * <p>
 * A {@link Channel} can have a {@linkplain #parent() parent} depending on
 * how it was created.  For instance, a {@link SocketChannel}, that was accepted
 * by {@link ServerSocketChannel}, will return the {@link ServerSocketChannel}
 * as its parent on {@link #parent()}.
 * <p>
 * The semantics of the hierarchical structure depends on the transport
 * implementation where the {@link Channel} belongs to.  For example, you could
 * write a new {@link Channel} implementation that creates the sub-channels that
 * share one socket connection, as <a href="http://beepcore.org/">BEEP</a> and
 * <a href="http://en.wikipedia.org/wiki/Secure_Shell">SSH</a> do.
 *
 * <h3>Downcast to access transport-specific operations</h3>
 * <p>
 * Some transports exposes additional operations that is specific to the
 * transport.  Down-cast the {@link Channel} to sub-type to invoke such
 * operations.  For example, with the old I/O datagram transport, multicast
 * join / leave operations are provided by {@link DatagramChannel}.
 *
 * <h3>Release resources</h3>
 * <p>
 * It is important to call {@link #close()} or {@link #close(ChannelPromise)} to release all
 * resources once you are done with the {@link Channel}. This ensures all resources are
 * released in a proper way, i.e. filehandles.
 */
public interface Channel extends AttributeMap, Comparable<Channel> {

Channel 是 Netty 里非常核心的一个接口,你直接看注释,一下子就能理解了 Netty 为啥搞出个 Channel 类来,Channel 类你可以怎么玩儿,这些 Netty 在注释给你说得清清楚楚、明明白白。

所以,我觉得注释一定是要的,只是需要有个标准,也要有个度。

从实践上看,我们团队有这么几个必须加注释的标准:

1. 复杂的业务逻辑

业务逻辑关联太多的东西又或者步骤非常多,更或者两者兼有,那么就很少有人会去耐心仔细的去一行一行的把整个代码全部读通理顺。

这时候,必须在业务逻辑实现的相关类中,把类在业务逻辑实现中是个什么成分,为什么这么设计类,以及对应的业务逻辑都要讲清楚。并且重构代码后,注释也必须跟着重构。

2. 晦涩的算法

算法也要加上注释的,尤其那些深奥的算法。大家不可能都是算法专家,能一下子就通过代码理解到算法实现的真谛。所以,这里也要加上注释,一般是说明这是用了个什么算法,这套算法的出处或者附上相关文章的引用地址。

3. 非常规的写法

非常规的写法往往是有特殊情况,不得已为之的。比如,为了得到更好的性能;又比如,为了修复一个 bug,却不想对代码进行大改动。

总之,非常规的写法就是反模式、反套路的,有时候甚至会违反程序员的直觉。像这些做法,必须在注释中写明这样实现的原因。

4. 可能有坑却暂时没太好解决办法

有些时候,需求出的够难够复杂,时间上催的又很急,你根本没办法马上想到特别好的办法去实现。只能临时想个简单粗暴的方案,先凑活着。甚至还会在某些地方,把一些变量的值写死先去把本期的需求实现了。

像这种就很可能就会给后面挖坑了。这时候,注释必须加上为什么要这么解决的原因,还必须加上 //TODO 这类的,表示后面需要进行进一步的修改。

5. 关于项目核心的接口、类和字段

做项目的时候,需求中的很多核心概念很可能会被映射到对应的接口或者实体类上,如果在这些核心接口和实体类加上清楚的注释,写明对应的业务概念,那么,后面再维护项目的时候,真的是事半功倍。

比如,我们在一套批量调度系统里,可能有多种任务的概念,有需要限定执行时间的任务,也有不需要限定执行时间的任务,那么实现上,就可能有个 LimitedTimeTask 类对应限定时间的任务,还有个 UnLimitedTimeTask 类去对应不需要限定执行时间的任务。那这两个类就必须加上注解,写清楚对应的业务概念。

如果特定概念是复合的,是由多个小概念构成,却必须用一个接口或者一个类来表示,那很可能实现上,就还得用字段去映射这些小概念,那么这些字段也得加上注释说明起对应的概念。

总之,注释我个人理解必须要有,但是不可能太泛滥,必须有节制、有规范的加。

最后,咱们说白了,我对注释的态度就是,和写代码一样,要有规范。

在这里和管理者说一句,如果你希望大家写好注释,不能就靠一句“必须写注释”这么高高在上的话去要求大家。没有规范,你就不能完全怪程序员不加注释了。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/796439.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL-每日一题【626.换座位】

题目 表: Seat 编写SQL查询来交换每两个连续的学生的座位号。如果学生的数量是奇数&#xff0c;则最后一个学生的id不交换。 按 id 升序 返回结果表。 查询结果格式如下所示。 示例 1: 解题思路 前置知识 MySQL 的 MOD() 函数是取模运算的函数&#xff0c;它返回两个数相除…

el-popover 的content内容换行

需求&#xff1a;把el-popover的content内容进行换行 <el-popoverplacement"bottom"width"450"trigger"click"><div class"custom-content">示例&#xff1a;如果您在 2 个托盘上运输 40 箱纸&#xff08;每个托盘上20 箱…

基于SpringBoot+Vue的“智慧食堂”系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

三、Web安全相关知识

请勿用于非法用途 文章目录 一、Web源码框架二、目录结构1、静态资源2、WEB-INF&#xff08;1&#xff09;classes&#xff08;2&#xff09;lib&#xff08;3&#xff09;web.xml 二、web脚本语言1、脚本种类&#xff08;1&#xff09;ASP&#xff08;2&#xff09;ASP.NET&am…

Vue的下载以及MVVM分析

&#x1f600;前言本片文章是vue系列第一篇整理了vue的基础和发展史 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f609;&#x1f6…

服务器——Nginx安装及静态配置、部署

目录 Nginx 安装Nginx步骤 安装yum-utils 配置nginx.repo源 安装nginx 系统启动nginx服务器 nginx.conf配置 关闭nginx服务器 配置文件启动nginx服务 配置文件编写 启动nginx服务 关闭nginx服务 服务器——Nginx安装及静态配置、部署。 直接在云服务器中启动项目&…

【C# 数据结构】Heap 堆

【C# 数据结构】Heap 堆 先看看C#中有那些常用的结构堆的介绍完全二叉树最大堆 Heap对类进行排序实现 IComparable<T> 接口 对CompareTo的一点解释 参考资料 先看看C#中有那些常用的结构 作为 数据结构系类文章 的开篇文章&#xff0c;我们先了解一下C# 有哪些常用的数据…

Android开发之Fragment动态添加与管理

文章目录 主界面布局资源两个工具Fragment主程序 主界面布局资源 在activity_main.xml中&#xff0c;声明两个按钮备用&#xff0c;再加入一个帧布局&#xff0c;待会儿用来展示Fragment。 <?xml version"1.0" encoding"utf-8"?> <LinearLayo…

一文快速入门任务调度框架-Quartz

前言 还不会 Quartz&#xff1f;如果你还没有接触过Quartz&#xff0c;那么你可能错过了一个很棒的任务调度框架&#xff01;Quartz 提供了一种灵活、可靠的方式来管理和执行定时任务&#xff0c;让咱们的定时任务更加优雅。本篇文章将为你介绍 Quartz 框架的核心概念、API 和…

Vue3封装函数组件(ElImageViewer)预览图片

目录结构 index.vue <template><el-image-viewer v-if"show" v-bind"$attrs" hide-on-click-modal close"show false" /> </template><script setup> import { ref, watch } from "vue" import { ElImageV…

D2L学习记录-10-词嵌入word2vec

NLP-1-词嵌入(word2vec) 参考: 《动手学深度学习 Pytorch 第1版》第10章 自然语言处理 第1、2、3 和 4节 (词嵌入) 词嵌入 (word2vec)&#xff1a; 词向量&#xff1a;自然语言中&#xff0c;词是表义的基本单元。词向量是用来表示词的向量。词嵌入 (word embedding)&#x…

余切拉普拉斯算子推导 cotangent Laplace-Beltrami operator

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 参考自polygon mesh proccessing这本书 基本思路及原理 余切拉普拉斯算子是一种考虑了网格底层几何联系的一种算子&#xff0c;在网格平滑&#xff0c;参数化等算法中…

no instance(s) of type variable(s) R exist so that void conforms to R 解决方法

一、问题描述 使用函数式编程stream().map()的时候报错&#xff1a; no instance(s) of type variable(s) R exist so that void conforms to R 二、报错原因 map()函数需要有一个返回值&#xff0c;但是setter方法返回值为void,即setChildren()返回值为void. 三、解决方法 …

SpringBoot前后端分离项目中实现将图片上传至Linux服务器(极简)

FileController /*** 文件上传至服务器 */ ApiOperation("文件上传") PostMapping("/upload") public R upload(MultipartFile file){String uploadUrl fileService.upload(file);return R.ok().message("文件上传成功").data("url",…

一遍看懂面试算法——二叉树

目录 二叉树的种类 满二叉树 完全二叉树 二叉搜索树 平衡二叉搜索树 二叉树的存储方式 二叉树的遍历方式 二叉树的递归遍历 二叉树的迭代遍历 前序遍历&#xff08;迭代法&#xff09; 中序遍历&#xff08;迭代法&#xff09; 后序遍历&#xff08;迭代法&#xff…

Python-如何使用正则表达式

如何利用Python使用正则表达式 目录 正则表达式常用匹配规则 ​编辑re库的使用 match()方法&#xff1a; search()方法: findall()方法 : sub()方法: compile()方法; 通用匹配 贪婪与非贪婪匹配 贪婪匹配 非贪婪匹配 修饰符 转义匹配 正则表达式是处理字符的强大…

高电压放大器ATA-2021B技术指标

随着ATA-2021H高压放大器的升级改版&#xff0c;新品ATA-2021B高电压放大器走进了更多工程师、研究人员的视野。相比于升级之前&#xff0c;ATA-2021B高压放大器拥有了更多更好地优势&#xff0c;可以更好地的帮助研究人员高效完成测试项目。今天Aigtek小编就带大家了解一下关于…

liunx时间慢几分钟,定时更新系统时间

#&#xff01;/bin/sh hwclock --hctosys echo "执行成功" 定时5分钟执行一次

minitab学习系列(2)--DOE逐步方法选择

系列文章目录 文章目录 系列文章目录前言一、DOE>因子>分析因子设计>逐步二、DOE>因子>分析因子设计>逐步>层次结构总结 前言 一、DOE>因子>分析因子设计>逐步 逐步删除和向模型中添加项以确定有用的项的子集。Minitab提供了三个常用过程&…

油画欣赏|《沧海的线条》在群山之间

《沧海的线条》80x65cm陈可之•2006年绘油画《沧海的线条》&#xff0c;通过绘画艺术的手法&#xff0c;描绘出三峡群山之间那一层层波浪般的纹理&#xff0c;展现出天地间岁月的古老沧桑变迁。此作品是陈可之先生百余幅三峡系列作品之一。夜&#xff0c;群山高大挺立。没有植被…