zoukankan      html  css  js  c++  java
  • 怎么去写好一段优雅的程序

    此文已由作者吴维伟授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。


    写好一段优雅程序的必要条件是良好的设计。


    写程序就像在走一个迷宫。编写之初,有若干个可能的解决方案萦绕在我们的脑海。我们选择一个继续深入,可能达到终点——实现了功能需求,但更大的可能是进入了一个死胡同或者一个新的岔路口,需要重新进行抉择,如此反复。


    想起一年前的自己,仅凭着生物的本能去写着代码:我依照着以往的经验,先写了一段。然后刷新一下页面,查看是否离实现需求更近了一步。幻想着程序可以完美运行的我看到最多的是JavaScript报错和意料之外的运行结果,那种被QA们称作BUG的东西。于是,我又凭着本能做出了修改……恍恍惚惚,不知经过了多久,程序终于运行在一个貌似正确的逻辑轨道上了。嗯?你问我程序里会不会有什么bug?这个,我还真不敢确定呢。


    我需要一份迷宫的地图,避开所有的死胡同,找到一条最优的路径到达出口,这就是设计。


    我们设计一段程序与PM规划一个产品的过程有些类似——首先对需求进行收集和整理,然后明确需要实现的N条功能,最后依次进行实现。不同的是,我们的用户就是我们自己,所以我们更具优势,更容易设计出一段易于使用的程序。


    设计程序的第一步是明确程序中需要实现的功能点。许多的功能点罗列在面前,是把它们实现在一个模块里呢,还是分多个模块去实现?如果分多个模块,每个模块都要实现哪些功能点呢?这些问题当然不能冒然的拍脑门决定,需要考虑可复用性和维护性。


    想象一个登陆功能,需求是这样的:我们需要把用户信息发送给后端进行验证,如果成功则刷新页面。功能很简单,很容易把代码写了出来:


    //模块逻辑class Login {
       login () {        this.verify(function () {            window.location.reload();
           });
       }    
       /**
        *  @description 验证用户信息。
        *  @param callback {Function} 验证通过后执行的回调函数
        */
       verify (callback) {        //do something
       }
    }//模块调用new Login().login();

    瞬间搞定,So Easy!


    弄完没多久,来了新需求。假设刚刚是页面A,现在要实现的页面B中的登陆逻辑与A有了一些不同:登陆成功后不再进行页面刷新,而是直接更新页面内关于用户信息的显示。


    现在要怎么实现呢?从零开始重新实现一个页面B的登陆逻辑?首先排除这种做法,毕竟验证用户信息这部分逻辑并没有发生改变,可以复用。想了想,写下了这样的代码:


    //模块逻辑class Login {    /**
        *  @param state {Number} 1 登陆后刷新  2 登陆后更新用户数据
        */
       login (state) {        this.verify(function () {            if(state === 1)                window.location.reload();            else if(state === 2)
                   doSomethingWithUserInfo();
           });
       }    
       /**
        *  @description 验证用户信息。
        *  @param callback {Function} 验证通过后执行的回调函数
        */
       verify (callback) {        //do something
       }
    }//模块调用//登陆后刷新new Login().login(1);//登陆后更新用户数据new Login().login(2);

        

    嗯,很好地满足了需求。但是这种实现方式过于僵硬,不太灵活。假设有页面C,页面D,其登陆逻辑中登陆成功后执行的操作又有不同。此时需要再次修改`Login.prototype.login`方法中的实现,那么以前能够稳定运行的逻辑就会有被改坏的可能。好的程序结构应该对扩展开放,对修改关闭。就是说,期望中,无论又增加哪些登陆成功后执行的操作,我们都不需要修改原来的代码。

    所以,重构了下:


    //模块逻辑class Login {
       
       login () {        this.verify(function () {            this.doAfterLogin();
           }.bind(this));
       }    
       /**
        *  @description 验证用户信息。
        *  @param callback {Function} 验证通过后执行的回调函数
        */
       verify (callback) {        //do something
       }    
       /**
        *  @abstract
        */
       doAfterLogin () {        //子类实现具体逻辑
       }
    }class LoginA extends Login {
       doAfterLogin () {        window.location.reload();
       }
    }class LoginB extends Login {
       doAfterLogin () {
           doSomethingWithUserInfo();
       }
    }//模块调用//登陆后刷新new LoginA().login();//登陆后更新用户数据new LoginB().login();


    将一些公用的逻辑提取到父类`Login`中。登陆成功后的操作每一次变化,只需要继承`Login`类,在新的子类中实现具体的逻辑。这样对已有功能不会产生任何影响。简直完美!


    沾沾自喜中,又来了新需求。假设现在又要实现页面E,页面F。页面E中登陆后的操作是刷新页面,与A相同。页面F中登陆后的操作是更新用户信息展示,与B相同。但是它们不再通过自己的后端来验证用户信息,而是通过URS和VRS(不要问我VRS是什么鬼……)。现在需要复用的部分不仅仅是对用户信息进行验证的功能,还有登陆成功后执行的操作。面对这样的需求,仅仅通过继承是不能将已有功能最大化复用的,需要将登陆验证和登陆后执行的操作这2个功能点划分到不同的模块中。于是,可以这样实现:


    //模块逻辑class Verify {    /**
        *  @abstract
        *  @description 验证用户信息。
        *  @param callback {Function} 验证通过后执行的回调函数
        */
       verify (callback) {        //子类实现具体逻辑
       }
    }class VerifyNormal extends Verify {
       verify (callback) {       //通过自己后台进行验证
       }
    }class VerifyURS extends Verify {
       verify (callback) {        //通过URS进行验证
       }
    }class VerifyVRS extends Verify {
       verify (callback) {        //通过VRS进行验证
       }
    }class Login {    
       /**
        *  @param verify {Verify}
        */
       login (verify) {
           verify.verify(function () {            this.doAfterLogin();
           }.bind(this));
       }    
       /**
        *  @abstract
        */
       doAfterLogin () {        //子类实现具体逻辑
       }
    }class LoginA extends Login {
       doAfterLogin () {        window.location.reload();
       }
    }class LoginB extends Login {
       doAfterLogin () {
           doSomethingWithUserInfo();
       }
    }
    //模块调用

    //普通登陆,登陆后刷新页面
    new LoginA().login(new VerifyNormal());
    //普通登陆,登陆后更新用户信息显示
    new LoginB().login(new VerifyNormal());
    //URS登陆,登陆后刷新页面
    new LoginA().login(new VerifyURS());
    //URS登陆,登陆后更新用户信息显示
    new LoginB().login(new VerifyURS());
    //VRS登陆,登陆后刷新页面
    new LoginA().login(new VerifyVRS());
    //VRS登陆,登陆后更新用户信息显示
    new LoginB().login(new VerifyVRS());


    总结一下:如果一个模块只有一个变化的原因(只有登陆后的操作会变化时),可以通过继承来满足开闭原则(对扩展开放,对修改关闭)。但是如果一个模块有多个变化的原因(如登陆后的操作和登陆验证流程都会发生变化),我们就需要把其中一个变化原因划分到另外一个模块中。一个模块只能有一个变化的原因(单一职责原则)。将功能点友好地划分到每一个模块,那么一段好程序的雏形也就被塑造出来了,剩下的就是往里面狠狠的填充。



    网易云免费体验馆,0成本体验20+款云产品! 

    更多网易技术、产品、运营经验分享请点击


    相关文章:
    【推荐】 一份ECMAScript2015的代码规范(下)
    【推荐】 理解DDoS防护本质:基于资源较量和规则过滤的智能化系统

  • 相关阅读:
    OPPO R9sPlus MIFlash线刷TWRP Recovery ROOT详细教程
    OPPO R11 R11plus系列 解锁BootLoader ROOT Xposed 你的手机你做主
    努比亚(nubia) M2青春版 NX573J 解锁BootLoader 并进入临时recovery ROOT
    华为 荣耀 等手机解锁BootLoader
    青橙 M4 解锁BootLoader 并刷入recovery ROOT
    程序员修炼之道阅读笔03
    冲刺8
    典型用户模板分析
    学习进度八
    冲刺7
  • 原文地址:https://www.cnblogs.com/163yun/p/9830234.html
Copyright © 2011-2022 走看看