厚德载物

爱喝茶的家伙

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

     在《The Pragmatic Programmer: From Journeyman to Master》(中文译名为《程序员修炼之道--从小工到专家》)Tip25 “怎样配平资源“中有一个重构代码的经典案例,现转录如下:

          提示35

         Finish What You Start

         要有始有终

      在大多数情况下这条提示都很容易应用。它只是意味着,分配某项资源的例程或对象应该负责解除该资源的分配。让我们通过一个糟糕的代码例子来看一看该提示的应用方式——这是一个打开文件、从中读取消费者信息、更新某个字段、然后写回结果的应用。我们去除了其中的错误处理代码,以让例子更清晰:

      void readCustormer(const char *fName, Customer  *cRec) {

    cFile = fopen(fName, "r+");

    fread(cRec, sizeof(*cRec), 1, cFile);

     }

     void writeCustomer(Customer  *cRect) {

    rewind(cFile);

    fwrite (cRec, sizeof(*cRec), 1, cFile);

    fclose(cFile);

  }

    void updateCustomer(const char *fName, double newBalance) {

    Customer cRec;

    readCustomer (fName, &CRec);

    cRec.balance = newBalance;

    writeCustomer(&cRec);

    } 

    初看上去,例程updateCustomer相当好。它似乎实现了我们所需的逻辑——读取记录,更新余额,写回记录。但是,这样的整洁掩盖了一个重大的问题。例程readCustomer和writeCustomer紧密地耦合在一起——它们共享全局变量cFile。readCustomer打开文件,并把文件指针存储在cFile中,而writeCustomer使用所存储的指针在其结束时关闭文件。这个全局变量甚至没有出现在updateCustomer例程中。

    这为什么不好?让我们考虑一下,不走运的维护程序员被告知规范发生了变化——余额只应在新的值不为负时更新。她进入源码,改动updateCustomer:

 

  void updateCustomer(const char *fName, double newBalance) {

    Customer cRec;

    readCustomer (fName, &CRec);

    if (newBalance >= 0.0)  {

      cRec.balance = newBalance;

      writeCustomer(&cRec);

    }

    } 

    在测试时一切似乎都很好。但是,当代码投入实际工作,若干小时后它就崩溃了,抱怨说打开的文件太多。因为writeCustomer在有些情况下不会被调用,文件也就不会被关闭。

   这个问题的一个非常糟糕的解决方案是在updateCustomer中对该特殊情况进行处理:

    void  updateCustomer(const char *fName, double newBalance) {

    Customer cRec;

    readCustomer (fName, &CRec);

    if (newBalance >= 0.0)  {

      cRec.balance = newBalance;

      writeCustomer(&cRec);

    }

    else 

      fclose(cFile);

    } 

      这可以修正问题——不管新的余额是多少,文件现在都会被关闭——但这样的修正意味着三个例程通过全局的cFile耦合在一起。我们在掉进陷阱,如果我们继续沿着这一方向前进,事情就会开始迅速变糟。

      要有始有终这一提示告诉我们,分配资源的例程也应该释放它。通过稍稍重构代码,我们可以在此应用该提示:

    void readCustomer(FILE *cFile, Customer *cRec) {

    fread(cRec, sizeof(*cRec), 1, cFile);

   }

   void  writeCustomer(FILE *cFile, Customer *cRec) {

    rewind(cFile);

    fwrite(cRec, sizeof(*cRec), 1, cFile);

   }

   void updateCustomer(const char *fName, double newBalance) {

    FILE *cFile;

    Customer  cRec;

 

     cFile = fopen(fName, "r+");

     readCustomer(cFile, &cRec);

       if (newBalance >= 0.0) {

      cRec.balance = newBalance;

      writeCustomer(cFile, &cRec);

    }

    fclose(cFile);

 }

    现在updateCustomer例程承担了关于该文件的所有责任。它打开文件并(有始有终地)在退出前关闭它。例程配平了对文件的使用:打开和关闭在同一个地方,而且显然每一次打开都有对应的关闭。重构还移除了丑陋的全局变量。

    由上文可见,重构时遵循一些原则是很重要的。

 

posted on 2019-04-15 16:21  剑胆琴心2015  阅读(978)  评论(0编辑  收藏  举报