【问题标题】:Default comments and meta-information in Java classes/interfacesJava 类/接口中的默认注释和元信息
【发布时间】:2011-06-17 21:59:45
【问题描述】:

在一些代码库中,我看到了可以描述为 默认 cmets 的 cmets。这些 cmets 您通常会在项目的每个文件中找到,我相信在大多数情况下,它们是在 IDE 的帮助下自动生成的。元信息有点不同。它实际上是代码的一部分。我想我最好举个例子。这是我们的测试对象(取自现实生活并经过简化):

public class UserServiceImpl implements IUserService {

    ////////////////////////////////////////////////////////////////////////
    // Constants
    ////////////////////////////////////////////////////////////////////////

    /** Logger for this class */
    @SuppressWarnings("unused")
    private static final Log LOG = LogFactory.getLog(UserServiceImpl.class);

    ////////////////////////////////////////////////////////////////////////
    // Attributes
    ////////////////////////////////////////////////////////////////////////

    /** User DAO */
    private IUserDao userDao;

    ////////////////////////////////////////////////////////////////////////
    // Constructors
    ////////////////////////////////////////////////////////////////////////

    /**
     * Default constructor
     */
    public UserServiceImpl() {
    }

    public UserServiceImpl(final IUserDao userDao) {
        this.userDao = userDao;
    }

    ////////////////////////////////////////////////////////////////////////
    // Getter and Setter methods
    ////////////////////////////////////////////////////////////////////////

    /**
     * @return value of {@link #userDao} field
     *
     */
    public IUserDao getUserDao() {
        return userDao;
    }

    /**
     * Sets {@link #userDao} field
     *
     * @param userDao User DAO
     */
    public void setUserDao(final IUserDao userDao) {
        this.userDao = userDao;
    }

    ////////////////////////////////////////////////////////////////////////
    // Implemented/Overridden methods
    ////////////////////////////////////////////////////////////////////////

    /**
     *
     * @see IUserService#saveUser(User)
     */
    @Override
    public void saveUser(final User user) {
        fillMissingFields(user);
        userDao.saveUser(user);
    }

    /**
     *
     * @see IUserService#getUserById(Integer)
     */
    @Override
    public List<User> getUserById(final Integer id) {
        return userDao.getUserById(id);
    }

    /**
     *
     * @see IUserService#getUserList(IEnvironmentContext)
     */
    @Override
    public List<User> getUserList(final @SuppressWarnings("unused") IEnvironmentContext context) {
        return userDao.getUserList();
    }

    ////////////////////////////////////////////////////////////////////////
    // Helper methods
    ////////////////////////////////////////////////////////////////////////

    private void fillMissingFields(final User user) {
        user.setLastUpdated(new Date());
    }

    ////////////////////////////////////////////////////////////////////////
    // toString() method
    ////////////////////////////////////////////////////////////////////////

    /**
     *
     * @see Object#toString()
     */
    @Override
    public String toString() {
        return "UserServiceImpl {...}";
    }
}

这个类包含了很多我想讨论的概念,所以我把它们分成这几种类型:

1) 部分默认 cmets - 对于类的每个部分,都有一个 3 行注释(如常量、构造函数等)。请注意,我不是在谈论课程的逻辑部分(例如 // user managenet// Account Balance calculation)。

2) Getter 和 Setter 默认 cmets - 用于 set/get 方法的 cmets,只告诉对应的方法集返回字段值。

3) Meta cmets - 描述一些 java 语言结构含义的 cmets。上面的例子:@see IUserService#saveUser(User) - 告诉方法被覆盖/实现和父方法的位置,Default constructor - 告诉它是 Java 类的默认构造函数,Logger for this class

4) @SuppressWarnings("unused") - 在我的具体示例中,它曾经说过,LOG 没有在课堂上使用(LOG 实际上从未在课堂上使用过,但IDE不会显示警告)和context参数没有使用,但没关系(假设context是一些通用信息,如果实现不使用它通常是完全正常的)。

5) I 接口前缀 - 前缀表示这是接口。

6) final 用于方法参数 - 防止方法体中的代码更改其值

我想知道您对课程中默认 cmets 和元信息的看法。 为了更直接,我建议您投票支持从 +5 到 - 等级的每种类型5:

+5 - 我认为这是必须,每个 Java 开发人员都应该这样做,甚至应该使用工具(如 checkstyle)来强制执行
...
0 - 我不在乎。如果有人告诉我这样做,我会 - 这些 cmets 不会带来任何正面或负面的价值。
...
-5 - 我强烈建议大家不要这样做。此类默认 cmets/元信息应在您看到后立即从课程中删除。

我也坚信,始终向您解释选项并回答问题非常重要:您为什么这么认为? (我个人总是尽量遵守这条规则)。所以我也鼓励你解释你对特定类型给出的观点。

我将在大约一周内接受大多数支持投票的答案。

PS.:你们中的一些人可能认为答案是不言而喻的,但相信我,我们每个人都非常不同,对你来说不言而喻的事情可能会让我和反之亦然。所以我鼓励你无论如何都要参与这个讨论。 (我会很感激的)

【问题讨论】:

  • 虽然这可能是一个有趣的讨论话题,但这似乎并不是一个具体的问题。

标签: java coding-style comments


【解决方案1】:

我认为我不会害怕问这个问题并且没有说出我对此的看法:)。这是我的投票。

长话短说 - 我反对默认/自动生成的 cmets。我认为 cmets 不应该描述不言自明的东西,解释 java 语言结构/代码约定或包含可以在 IDE 的帮助下轻松检索的信息。

1) -5(部分默认cmets)

这些 cmets 一方面试图描述 Java 代码约定(或试图强制执行它们不遵循约定的自定义类布局),另一方面正在破坏它们 - 例如让我们以 // Helper methods 部分为例:它强制放置本节中的所有私有方法都在文件末尾。这与 Java 代码约定 (section 3.1.3) 冲突:

这些方法应该按功能分组,而不是按范围或可访问性分组。例如,私有类方法可以位于两个公共实例方法之间。目标是让阅读和理解代码更容易。

很容易错误地将方法放在错误的部分 - 在这种情况下,它们会变得非常令人费解。

最糟糕的是当有人决定无论如何都要在所有课程中使用它时。 10 行长的小类将变为 50+ 行类(请参阅相关示例)

2) -5 Getter 和 Setter 默认 cmets

我觉得这些比乱七八糟还要糟糕。我相信他们实际上泄露了关于类内部的信息。 set/get 方法的全部意义在于隐藏事实,即它们只是设置并返回字段的值。

这些cmets是不言而喻的,并没有添加任何有用的信息——只会在源代码中乱七八糟。

我们还应该考虑我们的大脑是如何工作的。随着时间的推移,它实际上会学会忽略这些 cmets。在这种情况下,我们可能会错过一些重要或有用的东西。

3)-5Meta cmets

// Logger for this class// Default constructor 之类的评论是不言而喻的,不会带来任何有用的价值。如果有人真的需要这样的 cmets 来更好地理解或阅读代码,那么我认为是时候让他为初学者阅读一些 Java 书籍或教程了。

@see IUserService#saveUser(User) 对我来说没有任何意义(考虑到所有维护工作,应该进行这些工作以使这些 cmets 保持最新状态)。所有主要的 Java IDE 都可以为您提供此信息 - 它们提供明显的迹象表明方法覆盖/实现了您可以使用快捷方式跳转到那里的东西,如果您愿意,通常还可以在工具提示中查看哪个方法被覆盖。

现在考虑一种可能性,父类中的该方法(您已被覆盖)已被删除(但您的父类的父类中仍有方法)。在这种情况下,您应该更新所有子类中的这些 cmets,以使它们保持同步。而且我可以根据经验告诉你,在某些时候,这些 cmets 中的大多数会不同步,并且只会用于两个目的:1 - 污染代码和 2 - 迷惑和误导其他开发人员。

理论上,您可以找到使它们保持同步的方法(编写 IDE 插件、在构建期间创建复杂的代码重写并在每晚启动它等等...),但我坚信这只是浪费时间。

4) -3@SuppressWarnings("未使用")

我认为,@SuppressWarnings("unused") 在某些情况下有它的位置,但在问题中显示的示例中却没有。 LOG 注释,因为它从未使用过。但如果从未使用过,则应将 IMO LOG 从课程中删除。它是私有静态字段,因此只能由当前类使用。如果类不使用它,那就是一团糟。

对于context,它也毫无用处。 context 参数根据定义不应使用。它表示可能对子类有用的信息,但仅此而已。所以我觉得context有时候用不上是不言而喻的,没关系。我认为IDEA通常可以识别这种情况并且如果您不使用context,则不会显示警告。

就我个人而言,很难阅读带有所有这些@SuppressWarnings("unused") 注释的方法的签名(尤其是如果有多个注释)。

5) -4 I 接口前缀

我个人觉得它丑陋且不自然。如果我想要用户接口,我宁愿将其命名为User,实现将命名为UserImpl。我认为它可以与Uniform access principle 进行比较。只要叫User,不管是抽象类的接口还是具体类的接口。如果User 接口的作者在某个时候决定将其设为抽象类 - 我不应该更改我的代码,因为他很可能不会更改名称。但IUser 并非如此。

另一方面,IDE 为我们提供了足够多的提示来区分类和接口。

据我所知,这个命名来自Microsoft COM。在 C++ 中,它们没有接口,因此它们在具体类前面加上C(如CUser)和在接口类前面加上I(如IUser)。在 Java 中,我们有接口和Naming Conventions。而且我认为我们应该遵循它们,除非我们有充分的理由不这样做。

6) -1 final 用于方法参数 - 防止方法体中的代码更改其值

请不要误会我的意思。我相信,改变论点的价值真的很糟糕。我尝试尽可能多地编写不可变代码。

但是当我阅读代码时,方法签名中的所有这些finals 对我来说都是一团糟。对我来说,它应该是最终的,这是不言而喻的。但是它们使方法签名变得很大且难以阅读。

我认为应该可以使用工具(如 FindBugs 或 CheckStyle)强制执行此规则。我认为这是最好的方法。通常人们不会自己编写所有这些finals,而是允许IDE在他们保存文件时添加它们(例如eclipse可以制作)。但是,如果您实际上更改了参数的值(偶然),那么 IDE 将不会为您插入 final

【讨论】:

    【解决方案2】:

    代码文档的目的是帮助任何阅读代码的人理解它。仅此而已。这是您应该设定的目标,并确保您永远不会失去它。文档的价值可以表示为(&lt;the time it would take me to read the code without the documentation&gt; - &lt;the time it takes me to read the code with the documentation&gt;) * &lt;my salary&gt;)减去您编写文档所花费的时间乘以您的薪水,您得到净值。如果该数字可能是负数,请不要费心写它。这些公式显然是纯理论的,但它们允许我们构建一些问题。

    示例代码中的 cmets 是否帮助我理解了代码?一点也没有。对于任何有一点 Java 经验的人来说,其中的信息应该是显而易见的。

    他们阻碍了我吗? 是的,因为我滚动浏览它的时间是没有 cmets 时的三倍。

    如果要更改代码,保持 cmets 一致有多难?我相信,这就是关键。过于详尽的评论惯例更有可能被打破,这是可能发生的最糟糕的情况:评论不仅是多余的,而且具有误导性。这就像从维护开发人员那里偷时间。

    总而言之,为文档而文档是错误的。当您权衡您选择的文档时,您应该始终牢记读者:这段文档将如何使他们受益?如果您牢记这一点,您将做出正确的选择。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-02
      • 1970-01-01
      • 2019-11-14
      • 2019-04-20
      • 2019-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多