怎么去写好一段优雅的程序

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

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

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

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

想起一年前的自己,仅凭着生物的本能去写着代码:我依照着以往的经验,先写了一段。然后刷新一下页面,查看是否离实现需求更近了一步。幻想着程序可以完美运行的我看到最多的是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防护本质:基于资源较量和规则过滤的智能化系统

原文地址:https://www.cnblogs.com/163yun/p/9830234.html

时间: 2024-10-15 19:42:58

怎么去写好一段优雅的程序的相关文章

自己写的一段重试代码

public class TestRetry { public static void main(String[] args) { retry(5); } private static void retry(int maxCount) { int count = 0; boolean result = false; do { count++; System.out.println("count="+count); /* if(count==2) { result = true; }*/

又写了一段Perl

又写了一段Perl,帮同事减轻负担....就是搜索pas文件,提取信息而已. use warnings; use strict; use File::Find; $/ = undef; find(\&subprocess, "E:\\GpsClient"); sub subprocess { processfile($_) if /.pas$/; }; sub processfile { open(FH, shift); my $contents = <FH>; m

用纯函数式思维在Java8下写的一段奇葩程序

首先说一下什么是纯函数式.在我的理解,"纯函数式"用一句话就可以描述:Anything is value.--我的理解不一定准确,但我就是这么理解的. 就是所有的东西都是值--没有变量:包括函数在内都是值--是值,就可以传递(包括函数). 为什么说这段程序是奇葩呢? 其一.传统的Java是面向对象的,自从Java8中加入了lambda,Java就变成了"面向对象"和"函数式"两种方式的混合语言.这段程序全部使用lambda的语法来写,与平常写的J

请遵守好的编程风格 -- 关于.h文件去写函数的具体实现

今天往工程里添加Log.h和Log.cpp这用来打印log的文件时, 编译报错误. Log.cpp里的一些函数调用func1(), func2(), funcXXX()找不到实现, 可是那些函数明明在公共的接口库xxx.h和xxx.cpp里有声明和实现啊, 函数包含也没有问题. 问题排查 --> xxx.h里的函数声明都是static的, 很奇怪, 经对比, 只是我这个项目里这样, 其它项目里的这个公共接口库xxx.h里的函数声明并非static, 看来是之前维护这套代码的某猿给统一加上的. -

如何用纯前端去写购物车_索尼商城购物车

这里以Sony商城的购物车为例,购物车用纯前端的技术来写的,并且是存在了localstorage里,由于没有存在数据库里,购物车的操作基本是在前端页面操作的! 用jquery写的javascript,所以引用时记得去引用相对应的jquery文件,传输过来的zh-data可以自己自定义去写,另外;不要忘记zh-data里的数据和自己图片的命名需要一致的喔! 1.HTML页面 <div id="shopcarmsg"> <div class="top conta

如何在嵌套的app中运用vue去写单页面H5

本文主要介绍移动端.为了避免移动端兼容出现各种奇奇怪怪的bug,所以秉承着能不用复杂的语法就不用,尽量用最基础的语法. 可用惯了各种ES6语法的童鞋们,写原生真是头疼,再加上各种领导催工期,肯定是内心各种烦躁.这里介绍一下,如何在H5中运vue 去写,嵌套到用到app中. 首先引入vue CDN ,记得去扒一个vue.js. 下载下来,不要用htpp,这样可以避免有些模块因为网卡导致,页面加载错乱. <script src="./js/vue.min.js"></sc

自己写的一部分斗地主的程序,没有去写界面,临时是用黑框来显示的

这几天比較空,所以想写一点东西. 斗地主的程序一直以来都想写,但感觉规则推断比較复杂,一直没有较多的时间来写. 这次主要是把跟牌和牌型的推断写出来了.写了一个比較弱智的AI,属于有牌就出的那种.对于AI算法,临时没有什么好的想法,所以打算临时放一放. 后期补上界面之后再进行优化. 在这就把基本的函数和算法列出来吧. 首先是主程序,主要控制显示和游戏的流程. #include <iostream> #include "PokerDesk.h" using namespace

小规则让你写出美丽又高效的程序

本文来自肥宝游戏,引用必须保留文末二维码! ! ! 好几天没写文章了,周一整理自己刚修好的旧电脑,发现一本书<高质量C++编程指南>.由于近期在写游戏服务端的战斗.所以这个立马就吸引肥宝了.看了几天,深深感觉获益良多啊. 于是把笔记和自己的经验写下来,分享给大家. 一.写出美丽易读的程序 中学时代上课非常喜欢做笔记.可是肥宝差点儿没看过自己的笔记.不是肥宝懒.是由于肥宝字太丑了. 后来成为一个程序猿,以为这些代码都是电脑输出.统一字体.就不用这么纠结了.谁知道代码更须要写得美丽.由于需求是不断

小规则让你写出漂亮又高效的程序

本文来自肥宝游戏,引用必须保留文末二维码!!! 好几天没写文章了,周一整理自己刚修好的旧电脑,发现一本书<高质量C++编程指南>.因为最近在写游戏服务端的战斗,所以这个立刻就吸引肥宝了.看了几天,深深感觉获益良多啊.于是把笔记和自己的经验写下来,分享给大家. 一.写出漂亮易读的程序 中学时代上课很喜欢做笔记,但是肥宝几乎没看过自己的笔记,不是肥宝懒,是因为肥宝字太丑了. 后来成为一个程序员,以为这些代码都是电脑输出,统一字体,就不用这么纠结了.谁知道代码更需要写得漂亮,因为需求是不断在变化的,