V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
wog
V2EX  ›  问与答

项目稍微大一点就会变乱,有什么解决方法么

  •  
  •   wog · 2013-06-07 20:51:59 +08:00 · 4748 次点击
    这是一个创建于 4187 天前的主题,其中的信息可能已经有所发展或是发生改变。
    昨天看了下自己这一年写的代码,觉得实在惨不忍睹,
    python的还好些,c和c++的基本上感觉拿出来都会丢人的。
    这个问题其实我1年前就感觉到了,当时找一位前辈去问怎么解决,他推荐我学两个东西,一个是git,另一个是设计模式,然后让我多写代码。
    这一年来我也确实一直在学,各种代码写了3~4万行的样子,可是到现在效果不是很明显(就是从以前的一千多行开始变乱提高到两千多行开始变乱)
    我想问下大家遇到过这种情况么,应该怎么解决呢?
    ==================
    另外悄悄地问下,我在写简历的时候如果附上我的github地址,然后面试官看到我之前的项目很乱,会不会减分呢?
    26 条回复    1970-01-01 08:00:00 +08:00
    saharabear
        1
    saharabear  
       2013-06-07 21:00:42 +08:00   ❤️ 1
    乱就乱吧,真实。
    wog
        2
    wog  
    OP
       2013-06-07 22:36:24 +08:00
    @saharabear
    那就太好了,我还在考虑是不是应该清空github的记录呢
    alexrezit
        3
    alexrezit  
       2013-06-07 22:41:16 +08:00
    @wog
    想清就清吧. 反正我自己的 Github 上比较老一点的 repo 都删了, 留着丢人且无用, 更糟糕的是还容易误导别人学习一些不好的编码习惯. 不知道你是说一个文件两千多行么?
    wog
        4
    wog  
    OP
       2013-06-07 23:22:35 +08:00
    @alexrezit 不是,整个一个项目超过两千多行就感觉控制不了了,是特别是c++的,继承什么的一多就乱了,反倒是c的还好点,不会太乱
    fangzhzh
        5
    fangzhzh  
       2013-06-07 23:47:22 +08:00   ❤️ 7
    我是OO software engineer, 所以例子难免使用很多oo的概念.

    设计模式,面向对象, 都是一种手段,不是目的.

    # 几个大原则:
    * 先想框架, 再想细节.
    * 代码要分层, 划分好边界.
    * 把最容易变化的部分抽象并隔离出来.
    * 一个比较虚的, 简单->复杂. 考虑的足够复杂, 实现就可能很简单.
    * 解耦合. 分配好职责,一个类只做并做好一件事情, 通知机制尽量不要使用依赖过强的方式.

    ## 先框架, 再细节
    上来就写代码不是一种好习惯.

    写代码前, 技术选型以后, 先不要想代码上怎么实现. 要不要多线程, 是不是要使用stl, boost, 是不要要使用一些第三方库, 是不是要自己实现链表, 字典等等. 这个功能是不是适合一个排序.先不要从这些地方想.

    先想一想, 要做什么,逻辑上可以分成几个部分, 每个部分的关系, 之间的联系. 然后再想每个部分是什么, 需要具体做哪些工作, 实现哪些功能. 最后再考虑这些功能需要使用什么技术.


    ## 分层
    ### 大方面:
    一般是MVC, M数据层, V层肯定是要分开的. 控制层简单了和UI混在一起,有时候单拎.
    ### 小方面:
    一般的小功能一个工作类就可以搞定了. 稍微大一些的,举个异步的列子. 实现上一般有一个 manager管理所有的请求, 或并行, 或队列串行, 把请求分发给正确的工作类. 工作类结束了, 把结果交给manager , manager负责把结构返回给请求者.

    层次上, 工作类和管理类分开, 客户只看到了管理类的统一接口, 接口类不变, 工作类可以有无限增加, 并且可以透明替换. 那么client不再依赖工作类, 边界划分的很清楚.

    一个项目改动最多的就是工作类--即业务逻辑处理类. 分好层+边界, 多次的修改也不会对框架, 层次破坏, 也就不会乱了.

    ## 把最容易变化的部分抽象并隔离出来
    四人帮的设计模式你如果看过的话, 应该看到过这句话很多次. 把最容易变化的部分抽象出來, 就可以打到添加,修改需求时, 改动不影响以前代码最小.

    抽象不是为了重用, 而是为了被重用. 接口是抽象的一种实现方式. 如果设计的足够精良, 那么你的设计就会对修改封闭, 对添加开放.

    举个烂大街例子: 你设计个一个类叫animal, 你需要实现叫声这个方法, 你可以使用
    if( 汪星人)
    汪汪
    else if( 喵星人 )
    喵喵
    ….

    于是每增加一个动物,你的代码就要加上一行 else if, 修改原来代码.

    如果把此接口抽象出来, 每个动物尽管实现自己的doBark好了, 那么 代码简单到
    ani.doBark();

    具体doBark, 由自己确定, 以后增加了类, 只需要这个类实现了doBark接口, 就可以不修改以前代码的基础上, 直接被以前的代码使用.

    这个例子太简单, 太肤浅, 其实根本说明不了什么. 但是这种想法是要牢记的, 把*最容易变化*的部分抽象出来.

    ## 一个比较虚的, 简单->复杂. 考虑的足够复杂, 实现就可能很简单.
    这个本来就很虚, 说起来就更虚的. 最好不说了.

    ## 解耦合. 通知机制尽量不要使用依赖过强的方式.

    我说一个实际的问题吧. c++的数据库类D. 负责接受请求, 查询数据库, 将数据发送回去. 最显然的解决方案是: 客户类A像数据库类D发送请求, D分析请求, 是全部查询, 还是部分查询, 是不是可以查询, 还要考虑多个A之间的互斥.
    如果可以查询, 那么D查询数据库, 打包取回来的数据, 返回给A. 如果不能查询, 那么本次的请求就丢掉了.

    一种解耦合的方式, 队列+变量.
    A的请求, 请求时更新 最近一次的请求时间, 把具体请求入队列, 然后A就可以返回.
    D 根据自己的状态, 看是否有足够资源执行此次请求, 不行,就队列中等待. 直到有足够资源执行此次请求.
    打包结果, 放入结果队列. A可抵达, 使用回调函数, 通知A 返回的结果. 如果A此时不可用, 同样后台等待下次发送结果.
    这样保证了,请求不会丢, 结果不会丢, A不用无限等待D的执行过程. 解耦了A的请求与数据库查询操作.

    # 上边是战略,下边讲一下战术
    * 改代码尽量不影响以前的逻辑
    * 复杂的程序逻辑,先用一些条件去掉不可能的情况
    * 编写完代码的选择

    ## 改代码尽量不影响以前的逻辑
    这个很容易理解,不容易做到. 这个在修改已有的代码的是很重要的.
    千万不要*随便修改*已有的经过检验的业务逻辑, 哪怕你一百万的确定,你可以改对. 不要那么做.
    需要需要修改以前代码的情况,大多数是新增了需求, 那么找到合适的位置, 把以前没有过滤的情况,过滤掉. 不要直接修改以前的代码.

    ## 复杂的程序逻辑,先用一些条件去掉不可能的情况, 每一个顺序下去的条件,都是互斥的
    我以前写代码习惯这样
    if(可行)
    {
    if( 条件1)
    bla;
    else if(条件2.1 && 条件2.2 )
    {
    if(条件4)
    }
    }
    这种代码写下去,很恐怖.
    后来学会了,
    if(不可行)
    return;
    if(条件1)
    bla;
    if(!条件1 && (条件2.1 && 条件2 )) // !条件1, 这个保证了和上边的互斥, 在特别复杂的逻辑时,要容易理解的多
    bla;

    ## 编写完代码的选择
    一般人编完代码,就Run and Pray. 但是这是事倍功半的.

    一种实践证明的方法是自己code review. 自己的代码刚写完是很难发现错误的, 所以我有一些 checklist
    * 数据切片
    关键数据, 用搜索功能, 遍历他出现的地方, 是不是出现了不该出现的地方, 是不是在该出现的地方没有出现,这种办法可以杜绝很多的低级的 typo错误. 而这种错误是最难找的.
    * 控制切片
    遍历所有的if else, 是不是覆盖了你想要的所有情况, 多否定自己的想法, 如果还有其他情况呢, 如果不是专业昂呢. 脑海中做几个边界测试, for, while的上下边界, if的典型数据.


    太多了, 码字辛苦.

    虽然码字有这么多,我认为对楼主的帮助仍然不会很大. 可能会让楼主觉得, 哇哦,好牛逼啊. 但是实际操作上, 楼主仍然觉得是没有操作性.

    所以,我真正的建议, 找一个好的leader,或者伙伴,表现你的诚意,和可以栽培的潜力, 一个好leader ,可以让你的成长速度加倍, 有些东西是实践中来的,必须是你做了烂的设计, 别人做出不一样的设计; 或者你写了烂代码, 别人给出不一样的代码, 让你对比,你才会有切身的体会, 深刻的体会.

    纸上得来终觉浅,绝知此事要躬行.
    bengol
        6
    bengol  
       2013-06-07 23:51:05 +08:00
    @fangzhzh 不明觉厉
    fangzhzh
        7
    fangzhzh  
       2013-06-07 23:56:26 +08:00   ❤️ 1
    alexrezit
        8
    alexrezit  
       2013-06-08 06:40:27 +08:00 via iPhone
    @wog
    你试试 Objective-C, 一个文件五百行的节奏.
    soli
        9
    soli  
       2013-06-08 09:54:22 +08:00   ❤️ 1
    继承是罪魁祸首
    尽量不用继承;不得不用的时候不要超过2层;不得不超过2层的时候坚决不超过3层。
    xupefei
        10
    xupefei  
       2013-06-08 10:05:20 +08:00   ❤️ 1
    研读一下设计模式,之后就不乱了。
    dreampuf
        11
    dreampuf  
       2013-06-08 10:53:33 +08:00   ❤️ 1
    可操作层面:
    原则(KISSY,SOLID)为主,模式为辅
    制定约定(代码规范,项目规范,团队规范)

    方法论:
    一致优先,简洁其次,优美最尾
    wog
        12
    wog  
    OP
       2013-06-08 11:10:15 +08:00
    @fangzhzh 非常感谢,受教了,看来我确实应该出去找份实习,找个好的leader ,非常感谢前辈
    wog
        13
    wog  
    OP
       2013-06-08 11:11:41 +08:00
    @soli
    @dreampuf
    非常感谢,受教了
    wog
        14
    wog  
    OP
       2013-06-08 11:15:36 +08:00
    @xupefei 可能是我没研究透,大部分都会用,但有的模式用出来后我的代码就变成灾难级的了
    zhangdawei
        15
    zhangdawei  
       2013-06-08 11:16:55 +08:00
    解耦,功能性函数不超过200行,最多最多,
    wog
        16
    wog  
    OP
       2013-06-08 11:19:20 +08:00
    @alexrezit 没苹果,暂时试不了了,一个文件500行,虽然不知道是怎么写的,但总觉得好厉害的样子……
    wog
        17
    wog  
    OP
       2013-06-08 11:25:45 +08:00
    @zhangdawei 那功能性函数多了还很乱怎么办,还要想着怎么样复用,还要接口怎么设计,感觉管理起来有点力不从心
    zhujinliang
        18
    zhujinliang  
       2013-06-08 11:56:37 +08:00
    慢慢来,写得多了就能驾驭了。项目不急的话把编程当作艺术。
    Golevka
        19
    Golevka  
       2013-06-08 12:24:33 +08:00
    一直以来都感觉ED们不明觉厉. 为了不修改已有代码搞出一套dispatch真的很重要么? 至少我用Standard ML从来都是直接case animal of [blah blah blah]的.
    railgun
        20
    railgun  
       2013-06-08 13:19:44 +08:00
    把你的git地址发上来,我们看看有多乱233
    zhangdawei
        21
    zhangdawei  
       2013-06-08 14:15:15 +08:00
    @wog 我的做法是这样的,仅供参考,
    1,之前说过的,尽量一个功能性函数解决一个单一的问题,不要试图在一个函数里解决很多问题。
    2,针对一个功能模块(自己划分),管理一个文件,用来定义和声明所需要的函数。
    3,对于函数尽量减少复用,复用意味着在一个函数里增加判断和代码,增加耦合。
    4,模块与模块之间定义统一的接口,不穿插定义。
    5,不怕文件多,就怕文件大,
    cyberscorpio
        22
    cyberscorpio  
       2013-06-08 14:48:19 +08:00
    没有银蛋,设计模式更不是什么万能的灵药,而且很容易被滥用。
    在设计模式滥用的情况下,它带来的问题只怕比解决的更多。

    要想代码不乱,攻守俱有法度,无他,唯手熟耳。
    sivacohan
        23
    sivacohan  
       2013-06-08 20:55:04 +08:00
    @soli 你一定是看了Unix编程艺术之类的东西。不过我觉得这样挺好的。
    isy
        24
    isy  
       2013-06-08 23:00:26 +08:00
    @fangzhzh 感谢,写了几年代码的程序员表示你写的实在太好了。
    fangzhzh
        25
    fangzhzh  
       2013-06-08 23:34:47 +08:00
    @isy 谢谢你, 很高兴得到你的认同.
    题外话, 这是一年半工作的一些总结, 很久前就想总结出来, 但是一直没有那种非写不可的冲动.
    直到看到楼主的帖子的问题,就想着自己的东西也许可以用得上, 然后东西就自然而然的流出来, 没有刻意的去组织表达.
    所以也很要感谢楼主. 一个好的问题,能够帮助回答问题的人思考和表达.
    soli
        26
    soli  
       2013-06-10 10:06:57 +08:00
    @sivacohan 没看。。。我对大部头的书有恐惧症。上面我说的是我自己总结的教训。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2516 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 15:42 · PVG 23:42 · LAX 07:42 · JFK 10:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.