码迷,mamicode.com
首页 > 其他好文 > 详细

编写可维护软件的不朽代码随想-11

时间:2021-06-02 14:48:45      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:想法   增加   end   思考   restart   readonly   miss   应该   转换   

编写简洁的代码
代码坏味道是指隐含问题的代码风格。
不留痕迹
童子军军规:离开营地时,要让它比来时更干净。应用在软件开发中,表示一旦编写或修改了一段代码,就有机会进行小的改进,结果就是你让这段代码比之前更简洁、更具有可维护性。
如何使用本原则
1. 不要编写单元级别的代码坏味道:
        过长的代码单元(第2章),复杂的代码单元(第3章),长接口的代码单元(第5章)。需要及时重构“坏味道”的代码。及时就是尽可能快,但必须在代码被提交到版本控制系统之前。在开发过程中是允许有少许违反的,一个含有20行代码的方法,或者一个含有5个参数的方法,但是这些违反原则的地方,必须在重构并符合原则后,才允许被提交。作为一个负责任的开发人员,前面三个原则是与你的每日工作紧密相连,很容易发现代码单元长度、复杂度以及参数违反原则的地方,常见的做法是在集成测试环境中对这些方面进行检查。
2. 不要编写不好的注释:
        从SIG的经验来看,行内注释通常意味着工程方案不够优雅,从Jenkins代码库中提取的方法为例:
技术图片
public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, ServletException
{
    try
    {
        Jekins.getInstance().checkPermission(UPLOAD_PLUGINS);
        ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());

        // Parse the request
        FileItem fileItem = (FileItem)upload.parseRequest(req).get(0);
        String fileName = Util.getFileName(fileItem.getName());
        if ("".equals(fileName)) {
            return new HttpRedirect("advanced");
        }
        // we allow the upload of the new jpi‘s and the legacy hpi‘s
        if (!fileName.endWith(".jpi") && !fileName.endWith(".hpi")) {
            throw new Failure("Not a plugin: " + fileName);
        }

        //first copy into a temporary file name
        File t = File.createTempFile("uploaded", ".jpi");
        t.deleteOnExit();
        fileItem.write(t);
        fileItem.delete();

        final String baseName = identifyPluginShortName(t);

        pluginUploaded = true;

        // Now create a dummy plugin that we cam dynamically load
        // (the InstallationJob will force a restart if one is needed):
        JSONObject cfg = new JSONObject().element("name", baseName)
            .element("version", "0")
            .element("url", t.toURI)
            .element("dependencies", new JSONArray());
        new UpdateSite(UpdateCenter.ID_UPLOAD, null).new Plugin(UpdateCenter.ID_UPLOAD, cfg).deploy(true);
        return new HttpRedirect("../updateCenter");
    } catch (IOException e) {
        throw e;
    } catch (Exception e) { // grrr. fileItem.write throws this
        throw new ServletException(e);
    }
}
View Code

即使doUploadPlugin方法不是非常难以理解,但是通过行注释,很容易发现一些方法之外的关注点,例如,将fileItem复制到一个临时文件,以及创建插件配置等工作,就应该放在属于自己的方法中,这样可以对它们进行测试和重用。

代码中的注释可能会反映出许多不同的问题:

缺少对代码本身的理解:

  // I don‘t know what is happening here, but if I remove this line

  // an infinite loop occurs

没有正确使用缺陷跟踪系统:

  // JIRA-1234: Fixes a bug when summing negative numbers

忽略了约定或者工具的使用:

  // CHECKSTYLE:OFF // NOPMD

好的想法:

  // TODO: Make this method a lot faster some day

注释只有在很少一些情况下才有价值,例如帮助性的API文档,但是一定小心不要变成了教条的注释模板,一般来说,最佳建议是让你的代码远离注释。

3. 不要注释代码

不要提交被注释的代码,版本控制系统会永远保留一份旧代码的记录,因此你可以很安全地删除它们。从Apache Tomcat的代码为例:

技术图片
private void ValidateFilterMap(FilterMap filterMap) {
    // validate the proposed filter mapping
    string filterName = filterMap.GetFilterName();
    string[] servletNames = filterMap.GetServletNames();
    string[] urlPatterns - filterMap.GetURLPatterns();
    if (FindFilterDef(filterName) == null) {
        throw new Exception(sm.GetString("standardContext.filterMap.name", filterName));
    }
    if (!filterMap.GetMatchAllServletNames() && !filterMap.GetMatchUrlPatterns() 
        && (servletNames.Length == 0) && (urlPatterns.Length == 0)) {
        throw new Exception(sm.GetString("standardContext.filterMap.either"));
    }
    // FIXME: Older spec vevisons may still check this
    /* 
    if ((servletNames.Length != 0) && (urlPatterns.Length != 0))
        throw new IllegalArgumentException(sm.GetString("standardContext.filterMap.either"));
    */
    for (int i=0; i<urlPatterns.Length; i++) {
        if (!ValidateURLPattern(urlPatterns[i])) {
            throw new Exception(sm.GetString("standardContext.filterMap.pattern", urlPatterns[i]));
        }
    }
}
View Code

从原开发者角度看,FIXME注释及相关代码是能够理解的,但是对新的开发者是一种干扰,在留下这些注释代码之前,原开发者不得不做出一个决定:要不现在修复它,或者新建一个缺陷记录以后再修复它,或者完全忽视它。

4. 不要保留废弃代码

废弃代码有很多形式,是指根本不会被执行或输出被“废弃”(没有任何地方使用它的输出)的代码。 

方法中无法到达的代码:

public Transaction GetTransaction(long uid)
{
    Transaction result = new Transaction(uid);
    if (result != null)
    {
         return result;
    }
    else
    {
        return LookupTransaction(uid); //无法到达的代码
    }  
}

无用的私有方法:私有方法只能被同一个类中的其他代码调用,如果私有方法没有被类中的任何代码调用,就是废弃代码。

注释中的代码:不要把这个与被注释掉的代码搞混,有时候,在API文档中需要使用一些简单的代码片段,但是让这些代码片段与实践代码保持一致是一件非常容易被忽视的事情。

5. 不要使用过长的标识符名称

标识符的最大长度很难定义,有些业务领域的术语要比其他领域的长,但是大多数情况开发团队会对一个标识符是否过长进行讨论,要避免使用表示多个职责(比如generateConsoleAnnotationScriptAndStylesheet)或者包括过多技术术语(比如GlobalProjectNamingStrategyConfiguration)的标识符名称。 

6. 不要使用魔术常量

魔术常量是指在代码中没有被清晰定义的数字或字符串。比如:

float CalucateFare(Customer c, long distance)
{
    float travelledDistanceFare = distance * 0.10f;
    if (c.Age < 12)
    {
        travelledDistanceFare *= 0.25f;
    }
    else if (c.Age >= 65)
    {
        travelledDistanceFare *= 0.5;
    }
    return 3.00f + travelledDistanceFare;
}

这个代码示例的所有数字都是魔术常量,儿童和老人的年龄界限看似熟悉的数字,但是它们可能会在代码库的许多其他地方使用;作为常数的票价率很可能因为业务需要而随时改变。下面的代码展示了应该如何清晰地定义这些魔术常量,虽然增加了额外6行代码,但是这些常量都可以被其他地方重用。

private static readonly float BASE_RATE = 3.00f;
private static readonly float FARE_PER_KM = 0.10f;
private static readonly float DISCOUNT_RATE_CHILDREN = 0.25f;
private static readonly float DISCOUNT_RATE_ELDERLY = 0.50f;
private static readonly int MAXIMUM_AGE_CHILDREN = 12;
private static readonly int MINIMUM_AGE_ELDERLY = 65;

float CalucateFare(Customer c, long distance)
{
    float travelledDistanceFare = distance * FARE_PER_KM;
    if (c.Age < MAXIMUM_AGE_CHILDREN)
    {
        travelledDistanceFare *= DISCOUNT_RATE_CHILDREN;
    }
    else if (c.Age >= MINIMUM_AGE_ELDERLY)
    {
        travelledDistanceFare *= DISCOUNT_RATE_ELDERLY;
    }
    return BASE_RATE + travelledDistanceFare;
}

 7. 不要使用未正确处理的异常

捕获一切异常:记录下系统的失败行为,是为了了解它们产生的原因并且再加以改进,意味着你需要捕获系统所有的异常。虽然有时候空的catch代码库也能编译通过,但是这是一个不好的实践行为,没有提供任何与异常上下文有关的信息。

捕获特定异常:为了可以追踪某些特定事件的异常,应该捕获特定的异常,通用的异常不会提供触发失败的状态或事件信息,不应该直接捕获Throwable、Exception或者RuntimeException

在展示给终端用户之前,先将特定的异常信息转换成通用的信息。终端用户不应该被具体的异常信息所“打扰”,这会让他们感到困扰,也会带来安全隐患,例如,提供了有关系统内部工作原理的过多信息。

 

常见反对意见

1. 注释就是我们的文档。

对于缺少经验,希望了解代码工作原理的开发人员来说,能说实话的注释确实会带来帮助。但是在实践中,大多数代码中的注释却在撒谎——通常注释所表达的意思都是过时了的。随着系统时间越来越长,过时的注释也会越来越多。保持注释与代码一致需要很多细致的工作,但是在维护时会轻易地忽略这件事。能够“自述”的代码本身并不需要冗长的注释来说明,保持小型的、简单的代码单元,以及使用具有良好描述性的标识符名称,几乎没有必要再使用注释作为代码文档。

2. 异常处理带来了额外的代码工作。

异常处理是防御式编程的重要部分,预防不稳定的情况发生以及预期之外的行为出现。预测不稳定情况意味着试图预见可能发生错误的地方,的确会加重代码分析和编码的工作,但是值得投入,其好处可能现在看不到,在将来防止和处理不稳定情况时,会证明其价值。定义异常,可以将你的假设变成文档并保护起来,当环境发生变化时,很容易对其调整。

 

后续事宜

如何练习之前的10条原则的建议:要想确保代码易维护,依赖于日常工作中的两个行为,遵守纪律设定优先级

遵守纪律能够帮助你不断提升编码技巧;至于优先级,之前的原则可能会互相冲突,需要考虑哪一条原则会对实践中的可维护性产生最大的影响。

一定要花时间仔细思考这些,征询团队其他成员的意见。

低层级(代码单元)原则要优先于高层级(组件)原则:低层级原则会影响高层级原则,当代码单元很长且不断被到处复制时(涉及2和4章),代码库会变得巨大(涉及9章)。同样适用于架构层级的原则(7和8章),组件互相高度依赖,对代码结构重新组织时毫无意义的,试图平衡组件之前,应该先修复依赖的问题。

对每次提交负责: 最难的部分在于坚持遵守纪律,看上去可能更加有效的权宜之计会诱导你违反这些原则。

编写可维护软件的不朽代码随想-11

标签:想法   增加   end   思考   restart   readonly   miss   应该   转换   

原文地址:https://www.cnblogs.com/yorkness/p/14814336.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!