//TODO:写一个更好的评论 评论带来的风险 避免不必要的评论 写有用的评论 结论

2025-06-04

//TODO:写一个更好的注释

评论带来的风险

避免不必要的评论

撰写有用的评论

结论

这是在 2019 年纽约 Droidcon 上展示的。请点击此处观看视频:


本月初,Java 官方推特账号发布了一条非常有争议的推文,告诉你不要再写代码注释。

它链接到一篇Medium 文章,该文章解释了代码注释的一些问题,并建议解决方案是完全停止编写它们。

这真是个糟糕的建议。我很震惊,这竟然出自如此流行编程语言的官方社交媒体账号。代码注释可以为你的代码库提供巨大的价值,并真正帮助到下一个继承你代码的人。我还发现这篇文章有些过于苛刻:

当你需要写注释时,通常意味着你写的代码表达能力不够强。每次写注释的时候,你都会感到胃里一阵剧痛。

我在这里想告诉你,写评论并不意味着你失败了,也并不意味着你应该为此感到内疚。作为一个社区,我们不应该停止写评论。我们需要学习如何写出更好的评论。我希望这篇文章能成为你在这方面的指南。

评论带来的风险

首先,让我们对评论给我们带来的好处以及风险有一个基本的了解。

注释很有用,因为它们可以为代码的读者提供代码本身可能无法提供的见解。

注释存在潜在风险,因为它们可能会过时。修改代码并不保证注释也会随之更改,因此注释可能会误导我们。因此,默认情况下,我们应该尽量避免使用注释。

然而,我们不应该为了回避而回避评论。我们应该确保我们有意地去写评论,或者不写评论。

根据我的经验,我遇到的代码注释可以分为三类:

  • 此评论没有必要。
  • 这条评论毫无帮助。
  • 这条评论很有帮助。

我们的目标是确保所有评论都属于第三类——有用的评论。我们可以先删除所有不必要的评论,然后确保剩余的评论对读者有帮助。

避免不必要的评论

有些代码注释根本就没必要。在这种情况下,我们应该直接删除它们。否则,我们的文件会变得更加混乱,注释也更有可能过时,最终从不必要变为无用。

删除多余的评论

我们可以删除的第一种不必要的注释是多余的注释。

你可能很想为某个方法及其所有参数添加注释,但有时你的代码表达力太强,很难写出一条独特的注释。这时,你可能会想到这样的例子:



interface AccountDAO {
    /**
     * Inserts an account into the database.
     *
     * @param[account] The account that we're inserting.
     * @return The ID of the inserted account.
     */
    suspend fun insert(account: Account): Long
}


Enter fullscreen mode Exit fullscreen mode

关于 insert 方法的文档并非必需。第一行提供了一些信息,但根据方法名称和它所属的接口,可以推断出它的作用。account 参数的解释再次重复了代码中已经提到的内容。不过,return 语句可以提供一些帮助。

所以这条注释的三分之二提供了代码中已经提供的信息。这意味着它是多余的。我认为没有注释比有多余的注释更好,因为这样可以避免注释过时的风险。所以我建议只包含有用的部分:



interface AccountDAO {
    /**
     * @return The ID of the inserted account.
     */
    suspend fun insert(account: Account): Long
}


Enter fullscreen mode Exit fullscreen mode

例外

此规则的一个例外是,如果您正在构建一个供他人使用的公共 API 或库。在这种情况下,您应该记录所有可用的内容。即便如此,我们仍然可以仔细考虑所写的注释,并确保它们有用,这一点我们稍后会讨论。

修改代码以避免需要注释

让我们继续实现避免注释的基准目标。你已经开始编写注释了,并且确保它不是多余的。接下来,你应该看看是否可以重写代码,使这条注释变得不再必要,以便将其删除。

下面是一个简短的单行注释的示例,有助于阐明方法可以做什么:



// Saves data to database
fun saveData() {
}


Enter fullscreen mode Exit fullscreen mode

这条注释的作者(剧透:我)觉得这条注释有助于阐明该方法的作用。事后看来,我完全可以通过改一个更具表现力的方法名来避免这条注释:



fun saveDataToDatabase() {
}


Enter fullscreen mode Exit fullscreen mode

另一个常见的例子是,当我们编写一个执行多项操作的方法时,我们想要分解该方法的各个部分,如下所示:



fun transferMoney(fromAccount: Account, toAccount: Account, amount: Double) {
   // create withdrawal transaction and remove from fromAccount
   // ...

   // create deposit transaction and add from toAccount
   // ...
}


Enter fullscreen mode Exit fullscreen mode

且不论让一个方法执行多项操作是否违反了其他最佳实践,这还会导致我们产生一些额外的注释,而这些注释正是我们想要避免的。避免它们的一种方法是将每个步骤放入各自命名合适的方法中。



fun transferMoney(fromAccount: Account, toAccount: Account, amount: Double) {
   withdrawMoney(fromAccount, amount)
   depositMoney(toAccount, amount)
}


Enter fullscreen mode Exit fullscreen mode

撰写有用的评论

如果您已经阅读完最后一节,并且仍然觉得应该包含您的评论,因为它是一个公共 API,或者因为您觉得它提供了额外的价值,我们需要问自己它是否对读者有帮助。

注释告诉你为什么,代码告诉你什么

我不记得第一次听到这句话是在哪儿,但它一直引起我的共鸣。代码应该告诉你发生了什么,而注释可以告诉你为什么

让我们看看我最近写的一条糟糕的评论,它重复了以下内容



/**
 * A list of updated questions to be replaced in our list by an interceptor.
 */
private val updatedQuestions: MutableMap<Long, Question> = HashMap()


Enter fullscreen mode Exit fullscreen mode

这条注释几乎没什么用,但我必须把它归到无用类别。它提供了一些额外的信息,但很大程度上重复了代码中已经能告诉我的内容。上一节我们说过应该删除这些注释,但这条注释背后的原因实际上是我想解释一下为什么我添加了这个 HashMap。

让我们把这个评论变成一个有用的评论,重点关注原因



/**
 * The `PagedList` class from Android is backed by an immutable list. However, if the user answers
 * a question locally, we want to update the display without having to fetch the data from the network again.
 * 
 * To do that, we keep this local cache of questions that the user has answered during this app session,
 * and later when we are building the list we can override questions with one from this list, if it exists,
 * which is determined based on the key of this HashMap which is the question ID. 
 */
private val updatedQuestions: MutableMap<Long, Question> = HashMap()


Enter fullscreen mode Exit fullscreen mode

现在我们有了一条注释,它实际上解释了我为什么要保留一个本地 HashMap,因为我想覆盖一个无法轻易修改的不可变列表(感谢 Android)。因此,我们得到了一条有用的注释。

带有示例的评论很有帮助

我们一直强调要避免冗余注释。但当我们创建一个公共 API 供他人使用时,如果希望确保所有内容都记录在案,那么就比较困难。这会带来一些重复信息的风险。以下是另一个例子:



class Pokedex {
    /**
     * Adds a pokemon to this Pokedex.
     * 
     * @param[name] The name of the Pokemon.
     * @param[number] The number of the Pokemon.
     */
    fun addPokemon(name: String, number: Int) {

    }
}


Enter fullscreen mode Exit fullscreen mode

如果我们必须保留这些评论,我们会尽力确保它们有用。如果您无法提供更多信息,可以尝试提供示例:



class Pokedex {
    /**
     * Adds a pokemon to this Pokedex.
     * 
     * @param[name] The name of the Pokemon (Bulbasaur, Ivysaur, Venusaur).
     * @param[number] The number of the Pokemon (001, 002, 003).
     */
    fun addPokemon(name: String, number: Int) {

    }
}


Enter fullscreen mode Exit fullscreen mode

这并没有显著改善,但我们没有向读者重复信息,而是向他们提供了一个预期的例子。

外部资源链接可能会有帮助

我们都曾在 StackOverflow 上找到过问题的解决方案。有时,我们可以轻松地复制粘贴到项目中;但有时,我们可能需要重用整个文件或方法。在这种情况下,读者可能会困惑,这段代码或概念的来源,或者为什么需要它。

您可以通过链接到相应的问题或答案来提供这些见解。最近,我借助 StackOverflow 创建了一个程序化的 ViewPager,我想在此给予赞扬,并确保如果 ViewPager 出现任何问题,我可以参考一些信息来获取更多信息:



/**
 * A ViewPager that cannot be swiped by the user, but only controlled programatically.
 *
 * Inspiration: https://stackoverflow.com/a/9650884/3131147
 */
class NonSwipeableViewPager(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {
}


Enter fullscreen mode Exit fullscreen mode

可操作的评论才是好评论

这可能会让人感到震惊,因为评论通常不以可操作的形式呈现。可操作的评论很有帮助,因为它能给读者提供一些可借鉴的内容,避免他们问“我该如何处理这些信息?”

我认为我们可以关注两种类型的可操作的评论。

TODO 注释

总的来说,TODO 注释的风险很大。我们可能会看到一些我们想稍后再做的事情,于是就放弃了,//TODO: Replace this method想着以后再回来做,但最终却永远也做不成了。

如果您要写 TODO 注释,您应该做以下两件事之一:
1) Just Do It™
2) 链接到您的外部问题跟踪器。

TODO 注释有一些有效的用例。也许您正在开发一个大型功能,并且想要发起一个只修复部分功能的拉取请求。您还想提出一些仍需完成的重构,但您将在另一个 PR 中修复。链接到一些可以督促您完成工作的内容,例如 JIRA 工单:



//TODO: Consolidate both of these classes. AND-123


Enter fullscreen mode Exit fullscreen mode

这是可行的,因为它迫使我们去问题跟踪器创建工单。这比那些可能永远不会再看到的代码注释更不容易丢失。

弃用评论

另一种情况是,如果您对已弃用的方法或类留下评论,并且想要告诉用户下一步该去哪里,那么评论就可以具有可操作性。

我直接从 Android 中举了一个例子,他们弃用CoordinatorLayout.BehaviorCoordinatorLayout.AttachedBehavior



/**
 * ...
 * @deprecated Use {@link AttachedBehavior} instead
 */
@Deprecated
public interface DefaultBehavior {
    ...
}


Enter fullscreen mode Exit fullscreen mode

这条评论实际上很有帮助,因为它不仅告诉我某些东西已被弃用,还告诉我用什么来替代它。

结论

虽然注释存在风险,但并非所有注释都是坏事。重要的是,你要理解这种风险,并尽程序员应尽的责任来避免这种情况。具体来说,就是:

  • 删除您实际上不需要的评论。
  • 如果可以的话,重写代码以删除注释。
  • 确保剩余的评论是有帮助的。
    • 这是通过阐明原因、提供示例或采取行动来实现的。

有问题吗?请在评论区留言!您也可以在Twitter上轻松联系我。

文章来源:https://dev.to/adammc331/todo-write-a-better-comment-4c8c
PREV
使用 PageSpeed Insights API 监控性能
NEXT
VSCode:使用 Black 自动格式化 Python