V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kuoruan
V2EX  ›  程序员

如何才能减少代码的嵌套层级?

  •  
  •   kuoruan · 2018-05-03 14:51:44 +08:00 · 7034 次点击
    这是一个创建于 2396 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Linus Torvalds:

    If you need more than 3 levels of indentation, you ’ re screwed anyway, and should fix your program.

    如果你的代码里需要有超过三层的缩进,那么你已经搞砸了,应该修改你的代码。

    这里贴一段我写的代码

    function do_kcptun_update(btn) {
    	btn.disabled = true;
    	btn.value = '<%:Downloading...%>';
    	add_remove_page_notice(true);
    	var kcptun_update_url = '<%=dsp.build_url("admin/services/kcptun/update/kcptun")%>';
    	(new XHR()).get(kcptun_update_url, {
    		token: token_str,
    		url: kcptun_info ? kcptun_info.url.download : ''
    	}, function (x, json) {
    		if (x && x.status == 200) {
    			if (json.code == 0) {
    				btn.value = '<%:Extracting...%>';
    				(new XHR()).get(kcptun_update_url, {
    					token: token_str,
    					task: "extract",
    					file: json.file,
    					subfix: kcptun_info ? kcptun_info.type : ''
    				}, function (x, json) {
    					if (x && x.status == 200) {
    						if (json.code == 0) {
    							btn.value = '<%:Moving...%>';
    							(new XHR()).get(kcptun_update_url, {
    								token: token_str,
    								task: "move",
    								file: json.file
    							}, function (x, json) {
    								if (x && x.status == 200) {
    									if (json.code == 0) {
    										on_update_success(btn);
    									} else {
    										on_request_error(btn, json);
    									}
    								} else {
    									on_request_error(btn);
    								}
    							});
    						} else {
    							on_request_error(btn, json);
    						}
    					} else {
    						on_request_error(btn);
    					}
    				});
    			} else {
    				on_request_error(btn, json);
    			}
    		} else {
    			on_request_error(btn);
    		}
    	});
    }
    

    这是一段用来更新程序的 JavaScript,但是代码的继续执行依赖于上一次请求的成功执行。 可苦于没有好的优化方案,只能写出这种垃圾代码了。

    第 1 条附言  ·  2018-05-03 16:24:30 +08:00

    稍微优化了一下,感觉能看一点了:GitHub

    51 条回复    2018-05-04 15:30:16 +08:00
    hkllzh
        1
    hkllzh  
       2018-05-03 14:54:14 +08:00
    Rx
    sunny352787
        2
    sunny352787  
       2018-05-03 14:55:27 +08:00   ❤️ 1
    最简单的,反过来判断不就行了
    kuoruan
        3
    kuoruan  
    OP
       2018-05-03 14:57:36 +08:00
    @sunny352787 #2 具体如何?
    hcymk2
        4
    hcymk2  
       2018-05-03 14:57:42 +08:00
    首先不再使用匿名函数。之后再找个对付回调地狱轮子
    Luckyray
        5
    Luckyray  
       2018-05-03 14:58:01 +08:00
    重构,封装成函数
    PressOne
        6
    PressOne  
       2018-05-03 14:59:32 +08:00 via Android
    有的时候多层嵌套少不了 ,不用故意减少。按实际的逻辑表现即可,你这个看起来也确实多了点
    kuoruan
        7
    kuoruan  
    OP
       2018-05-03 15:01:52 +08:00
    @Luckyray #5 封装成函数并没有事实上减少嵌套层级
    noli
        8
    noli  
       2018-05-03 15:05:03 +08:00 via iPhone
    你需要使用 c#或者像 c#那样提供 yield return 并且提供像 select 和 select many 把深层次数据提取代码 压扁的语法糖
    Phariel
        9
    Phariel  
       2018-05-03 15:05:32 +08:00
    Promise
    glacer
        10
    glacer  
       2018-05-03 15:07:42 +08:00
    Promise 或 async/await 了解一下
    widdy
        11
    widdy  
       2018-05-03 15:12:15 +08:00
    @kuoruan
    @sunny352787 意思是先判断 else 逻辑,把 else 提前 return;
    siteshen
        12
    siteshen  
       2018-05-03 15:14:35 +08:00   ❤️ 4
    楼上说的“最简单的,反过来判断不就行了”,其实说的是 `early return`。
    其他的回答 Promise async/await 等完全是另外一个话题。
    按照这个思路重构代码会清晰很多,刚刚写了个例子供参考。

    ```js
    // origin source code
    function doSomething() {
    if (cond1) {
    success1();
    if (cond2) {
    success2();
    if (cond3) {
    success3();
    } else {
    fail3();
    }
    } else {
    fail2();
    }
    } else {
    fail1();
    }
    }

    function betterDoSomething() {
    if (!cond1) {
    fail1();
    return;
    }
    success1();

    if (!cond2) {
    fail2();
    return;
    }
    success2();

    if (!cond3) {
    fail3();
    return;
    }
    success3();
    }
    ```
    Immortal
        13
    Immortal  
       2018-05-03 15:16:05 +08:00
    @kuoruan
    回你 7l
    感觉你有点钻牛角尖了.重构和封装的确不会直接减少嵌套和层级,但是我们做的这些能直接提高代码可读性.
    而且你那个看起来好像可以用抛出异常的方式处理
    kuoruan
        14
    kuoruan  
    OP
       2018-05-03 15:17:04 +08:00
    @glacer #10 为了兼容性,用不了 ES6
    autoxbc
        15
    autoxbc  
       2018-05-03 15:18:30 +08:00
    Promise +1

    另外,(new XHR()).get...
    这种多余的括号看着真难受
    kuoruan
        16
    kuoruan  
    OP
       2018-05-03 15:19:19 +08:00
    @Immortal #13 这确实是个好方法
    DOLLOR
        17
    DOLLOR  
       2018-05-03 15:23:21 +08:00
    Promise,Babel 和 TypeScript 都支持 es3 target 的。

    如果不想用 ES6 语法,就用 async.js 。
    crab
        18
    crab  
       2018-05-03 15:27:38 +08:00
    if (x && x.status != 200)
    XXXXX
    RicardoScofileld
        19
    RicardoScofileld  
       2018-05-03 15:28:29 +08:00
    最简单的反向判断啊,if ...,你改成 if not ... 不就好咯嘛
    loveCoding
        20
    loveCoding  
       2018-05-03 16:03:19 +08:00
    反向判断+++1
    zn
        21
    zn  
       2018-05-03 16:07:08 +08:00 via iPhone   ❤️ 2
    短路疗法了解一下。



    其实就是楼上说的反向判断啦,先判断不符合条件的,尽快返回,后面剩下合法条件再做处理。
    GoLand
        22
    GoLand  
       2018-05-03 16:07:56 +08:00   ❤️ 1
    early return.
    qiumaoyuan
        23
    qiumaoyuan  
       2018-05-03 16:16:07 +08:00
    @kuoruan 封装是唯一正确的办法。不仅仅是封装成方法,还可以封装成类。

    要嵌套多少层是业务逻辑决定的,而不是代码编写方式决定的。

    其实你不知道你调用的代码库背后已经有了多少层的逻辑判断。
    evitceted
        24
    evitceted  
       2018-05-03 16:22:48 +08:00
    rx
    yongjing
        25
    yongjing  
       2018-05-03 16:25:52 +08:00
    callback hell
    sampeng
        26
    sampeng  
       2018-05-03 16:48:45 +08:00
    封装成函数+promise 可以解决问题。。但 promise 也可能陷入调用链太长的问题。总比这样强不是。

    楼上有说封装成函数没减少层级。有些逻辑是没办法减少的。但封装函数最少一眼能看明白逻辑,让代码清晰可见的最终目的不就是可读性尚可。

    early return 也是一个很好的手段。稍微减少一些不必要判断层级。

    唔。。少用匿名函数。。简直是噩梦
    pluschen
        27
    pluschen  
       2018-05-03 16:56:22 +08:00
    啥?封装成函数没减少层数?
    别逗,哪有什么层数?都是 JMP。
    sampeng
        28
    sampeng  
       2018-05-03 16:58:58 +08:00
    仔细看了代码。。只是各种 else 处理错误而已。。。这个。。很难吗?
    xiaojunjor
        29
    xiaojunjor  
       2018-05-03 17:02:32 +08:00
    kuoruan
        30
    kuoruan  
    OP
       2018-05-03 17:12:59 +08:00
    @sampeng #28 不好意思,我能力不行,让您见笑了
    zhlssg
        31
    zhlssg  
       2018-05-03 17:16:50 +08:00
    async await ,提早 return
    saulshao
        32
    saulshao  
       2018-05-03 17:19:49 +08:00
    If you need more than 3 levels of indentation, you ’ re screwed anyway, and should fix your program.
    这句话本来就是说的是一个函数里面的事情。
    cs923
        33
    cs923  
       2018-05-03 17:31:50 +08:00
    RxJs(没用过 应该能解决)
    Justin13
        34
    Justin13  
       2018-05-03 18:21:09 +08:00 via Android
    就嵌套而言,可以考虑 early return,抽函数,就业务而言,可以考虑使用更优雅的库,promise,async
    we2ex
        35
    we2ex  
       2018-05-03 18:23:59 +08:00 via Android
    @xiaojunjor #29 正解,第一时间想到的也是这篇文章
    banricho
        36
    banricho  
       2018-05-03 18:25:14 +08:00
    这个代码的根本原因在于直接原生 XHR …

    不用任何库的情况下,封装一个 fetch
    剩下是处理异步的问题,楼上都说了
    最后才是 if 的问题
    wlwood
        37
    wlwood  
       2018-05-03 18:42:50 +08:00
    其实,感觉,用 react.js, vue 这些框框,这种地狱应该挺好解决啊
    noNOno
        38
    noNOno  
       2018-05-03 20:08:10 +08:00
    歪个楼,动感光波?
    Exia
        39
    Exia  
       2018-05-03 20:14:50 +08:00
    楼主的地狱回调,能自己看懂就还不错了,应该用 promise 吧,我也要向这个方面提升一下。
    LevineChen
        40
    LevineChen  
       2018-05-03 20:22:12 +08:00 via iPhone
    多个 return 也不好 可以用 do while ( false ) break 实现类似功能
    xpol
        41
    xpol  
       2018-05-03 22:19:51 +08:00
    GraphQL 了解一下。
    xpol
        42
    xpol  
       2018-05-03 22:23:05 +08:00
    我以为都是从服务器请求数据:),请忽略我上一条回复。
    mingyun
        43
    mingyun  
       2018-05-03 23:47:44 +08:00
    提前 return
    pepesii
        44
    pepesii  
       2018-05-04 08:41:10 +08:00 via iPhone
    我记得上初中的时候,数学老师经常说的一句话是,“正难则反”,于是我在碰到真值条件复杂的时候,就找假条件先处理,提前返回。
    crayygy
        45
    crayygy  
       2018-05-04 09:51:41 +08:00
    打开帖子以前看到标题的第一反应就是 JS 的回调地狱...
    th00000
        46
    th00000  
       2018-05-04 09:59:27 +08:00
    快速失败原则
    ikaros
        47
    ikaros  
       2018-05-04 10:03:53 +08:00
    你这样写以后再来看不觉得麻烦吗?
    SakuraKuma
        48
    SakuraKuma  
       2018-05-04 10:19:07 +08:00
    Promise 啊。

    kcptun.download().then(extract).then(move).catch(onError)
    f0rger
        49
    f0rger  
       2018-05-04 11:33:10 +08:00
    大概长这样,没调,可能有语法和写法错误:
    https://gist.github.com/luoweihua7/58b884ce7ecd618bb27fbe8fa9b4cf72
    zhuweiyou
        50
    zhuweiyou  
       2018-05-04 12:10:19 +08:00
    一个是用 async / await

    一个是先 return 错误的,就不会嵌套多层了
    bangbaoshi
        51
    bangbaoshi  
       2018-05-04 15:30:16 +08:00
    ·····
    if (cond1) {
    if (cond2) {
    if (cond3) {
    do something
    }
    }
    }
    ·····
    改成
    ·····
    if(!cond1) {
    return;
    }
    if(!cond2) {
    return;
    }
    if(!cond3) {
    return;
    }
    do something
    ·····
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2693 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 04:11 · PVG 12:11 · LAX 20:11 · JFK 23:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.