记录一次重构

Posted 飘飘白云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录一次重构相关的知识,希望对你有一定的参考价值。

记录一次重构

记录一次简单的重构是为了体现出代码重构的重要性和紧迫性。如果代码不能持续进化,那么随着新的代码不断增加,代码越来越难以维护和扩展,于是老代码成了难以追踪、难以理解、一动就崩溃的bad smell代码。此外,不通过持续打磨代码,程序员自身水平以及团队水平也难以得到提高。通过重构与review机制能够让编程经验与知识在团队中得以传递。

更详细的关于为什么要重构代码以及怎样重构代码,推荐阅读经典大作《重构-改善既有代码的设计》

现状

功能

产测工具中有一个检测触摸屏的测试,该测试显示一些可以点击方块,点击方块或在方块上滑过就将该方块变黑,表示该方块区域触摸测试通过。

接下来要重构的这一段代码的功能就是显示这些可以点击的方块,其实现是每一个方块都是一个独立的View

代码

原来的关键代码:

    private void showBlock()
        int widthLength = WIDTH / WIDTH_NUM;
        int heightLength = HEIGHT / HEIGHT_NUM;
        int crossLength = (WIDTH - 2 * widthLength)/(HEIGHT_NUM - 2);

        int accu = 0;
        for(int i = 0; i < WIDTH_NUM; i++) 
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(widthLength, heightLength);
            layoutParams.leftMargin = i * widthLength;
            layoutParams.topMargin = 0;
            TouchView view = new TouchView(mContext);
            mFrame.addView(view, layoutParams);
            view.init(accu, TouchView.DISTOGGLED, bck, layoutParams, listener);
            mTouchData.add(accu++);

            FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(widthLength, heightLength);
            layoutParams2.leftMargin = i * widthLength;
            layoutParams2.topMargin = (HEIGHT_NUM - 1) * heightLength;
            TouchView view2 = new TouchView(mContext);

            mFrame.addView(view2, layoutParams2);
            view2.init(accu, TouchView.DISTOGGLED, bck, layoutParams2, listener);
            mTouchData.add(accu++);

        

        for(int j = 1; j < HEIGHT_NUM - 1; j++) 
            FrameLayout.LayoutParams layoutParams3 = new FrameLayout.LayoutParams(widthLength, heightLength);
            layoutParams3.leftMargin = 0;
            layoutParams3.topMargin = j * heightLength;
            TouchView view3 = new TouchView(mContext);

            mFrame.addView(view3, layoutParams3);
            view3.init(accu, TouchView.DISTOGGLED, bck, layoutParams3, listener);
            mTouchData.add(accu++);

            FrameLayout.LayoutParams layoutParams4 = new FrameLayout.LayoutParams(widthLength, heightLength);
            layoutParams4.leftMargin = (WIDTH_NUM - 1) * widthLength;
            layoutParams4.topMargin = j * heightLength;
            TouchView view4 = new TouchView(mContext);
            mFrame.addView(view4, layoutParams4);
            view4.init(accu, TouchView.DISTOGGLED, bck, layoutParams4, listener);
            mTouchData.add(accu++);

            FrameLayout.LayoutParams layoutParams5 = new FrameLayout.LayoutParams(crossLength, heightLength);
            layoutParams5.leftMargin = widthLength + (j - 1) * crossLength;
            layoutParams5.topMargin = j * heightLength;
            TouchView view5 = new TouchView(mContext);

            mFrame.addView(view5, layoutParams5);
            view5.init(accu, TouchView.DISTOGGLED, bck, layoutParams5, listener);
            mTouchData.add(accu++);

            if(j == 4) continue;
            FrameLayout.LayoutParams layoutParams6 = new FrameLayout.LayoutParams(crossLength, heightLength);
            layoutParams6.leftMargin = WIDTH - (widthLength + j * crossLength);
            layoutParams6.topMargin = j * heightLength;
            TouchView view6 = new TouchView(mContext);
            mFrame.addView(view6, layoutParams6);
            view6.init(accu, TouchView.DISTOGGLED, bck, layoutParams6, listener);
            mTouchData.add(accu++);
        
    

效果

这一段代码存在问题,不能够适配各种不同的分辨率。它在1072x1448分辨率上仅仅是侥幸能够工作,而在600x800分辨率的情况下问题就彻底暴露来了。

600x800分辨率下的效果:

评析

代码格式

该有空格的地方没有空格,该有分行的地方没有分行。比如f (j == 4) continue; 这样代码逻辑应该前后加空行以突出。

命名或定义不规范。

  • 成员变量 bck 改名为 mDrawables 更为合适;
    private Drawable[] bck;
  • 成员变量 WIDTHHEIGHT 改名为 mScreenWidthmScreenHeight 更为合适;
  • 成员变量 WIDTH_NUMHEIGHT_NUM 应定义为 final static 类型;
    private int WIDTH_NUM = 7;
    private int HEIGHT_NUM = 9;
  • 局部变量 accu 应该是accumulate 的缩写,但这里并非求和,修改为index 更为合适;
  • 局部变量 widthLengthheightLengthcrossLength 是宽和高,不应该使用Length后缀;
  • 局部变量 layoutParams2 ~ 6view2 ~ 6,命名通常非常忌讳使用数字后缀作为区分,因为数字后缀不能够明白地反应出该变量所代表的含义,此外有这样命名的地方通常存在着重复代码;

重复代码

上面提到使用数字后缀区分变量名的地方很可能存在重复代码,这里确实就存在这样的问题。局部变量 layoutParams2 ~ 6 其实都是用于同一个目的:作为addView的参数将一个方块(block)View添加到Frame控件上面去。因此这个添加方块的过程可以封装成一个函数。

代码逻辑

  • 错误
    因为屏幕的宽和高可能不能够被块的行数或列数整除,因此渲染出来的方块可能会出现不连续的情形(见前面600x800分辨率下的效果图)。代码中试图用crossLength来调整这样的情况,但逻辑上确实错误的;
    • 复杂
      代码使用两个单独for循环单独根据行和列来添加方块,并且在其中判断魔数j == 4来处理,理解起来非常费劲;

重构

如果有丰富的编码或重构经验,我们可以根据上面评析出来的问题一步到位把代码重构好。但通常不建议这么做,心急吃不了热豆腐。重构通常是:

  • 小步前进
  • 每前进一步,都要回归测试
  • 先易后难,往往从代码格式、规范命名、从重复代码中提炼功能函数入手

下面就根据上面的重构原则暂进地重构上面的代码

规范命名

根据前面列出的命令或定义不规范问题的修改意见重构 version1.0 如下:

    private void showBlock() 
        int width = mScreenWidth / WIDTH_NUM;
        int height = mScreenHeight / HEIGHT_NUM;
        int crossWidth = (mScreenWidth - 2 * width)/(HEIGHT_NUM - 2);

        int index = 0;
        for (int i = 0; i < WIDTH_NUM; i++) 
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
            layoutParams.leftMargin = i * width;
            layoutParams.topMargin = 0;
            TouchView view = new TouchView(mContext);
            mFrame.addView(view, layoutParams);
            view.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams, listener);
            mTouchData.add(index++);

            FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(width, height);
            layoutParams2.leftMargin = i * width;
            layoutParams2.topMargin = (HEIGHT_NUM - 1) * height;
            TouchView view2 = new TouchView(mContext);

            mFrame.addView(view2, layoutParams2);
            view2.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams2, listener);
            mTouchData.add(index++);
        

        for (int j = 1; j < HEIGHT_NUM - 1; j++) 
            FrameLayout.LayoutParams layoutParams3 = new FrameLayout.LayoutParams(width, height);
            layoutParams3.leftMargin = 0;
            layoutParams3.topMargin = j * height;
            TouchView view3 = new TouchView(mContext);

            mFrame.addView(view3, layoutParams3);
            view3.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams3, listener);
            mTouchData.add(index++);

            FrameLayout.LayoutParams layoutParams4 = new FrameLayout.LayoutParams(width, height);
            layoutParams4.leftMargin = (WIDTH_NUM - 1) * width;
            layoutParams4.topMargin = j * height;
            TouchView view4 = new TouchView(mContext);
            mFrame.addView(view4, layoutParams4);
            view4.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams4, listener);
            mTouchData.add(index++);

            FrameLayout.LayoutParams layoutParams5 = new FrameLayout.LayoutParams(crossWidth, height);
            layoutParams5.leftMargin = width + (j - 1) * crossWidth;
            layoutParams5.topMargin = j * height;
            TouchView view5 = new TouchView(mContext);

            mFrame.addView(view5, layoutParams5);
            view5.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams5, listener);
            mTouchData.add(index++);

            if (j == 4) continue;
            FrameLayout.LayoutParams layoutParams6 = new FrameLayout.LayoutParams(crossWidth, height);
            layoutParams6.leftMargin = mScreenWidth - (width + j * crossWidth);
            layoutParams6.topMargin = j * height;
            TouchView view6 = new TouchView(mContext);
            mFrame.addView(view6, layoutParams6);
            view6.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams6, listener);
            mTouchData.add(index++);
        
    

重构命名之后,代码阅读起来稍稍容易明白一点。但效果不大,接下来就要靠减少重复代码,提炼功能函数了。

提炼功能函数

提炼功能函数之后的 version2.0 阅读效果就大为改观了:

    private void addBlock(int index, int x, int y, int w, int h) 
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(w, h);
        layoutParams.leftMargin = x;
        layoutParams.topMargin = y;
        TouchView view = new TouchView(mContext);
        mFrame.addView(view, layoutParams);
        view.init(index, TouchView.DISTOGGLED, mDrawables, layoutParams, listener);
        mTouchData.add(index);
    

    private void showBlock() 
        int width = mScreenWidth / WIDTH_NUM;
        int height = mScreenHeight / HEIGHT_NUM;
        int crossWidth = (mScreenWidth - 2 * width)/(HEIGHT_NUM - 2);

        int index = 0;
        int x, y;
        for (int i = 0; i < WIDTH_NUM; i++) 
            x = i * width;
            y = 0;
            addBlock(index++, x, y, width, height);

            x = i * width;
            y = (HEIGHT_NUM - 1) * height;
            addBlock(index++, x, y, width, height);
        

        for (int j = 1; j < HEIGHT_NUM - 1; j++) 
            x = (WIDTH_NUM - 1) * width;
            y = j * height;
            addBlock(index++, x, y, width, height);

            x = width + (j - 1) * crossWidth;
            y = j * height;
            addBlock(index++, x, y, crossWidth, height);

            if (j == 4) continue;

            x = mScreenWidth - (width + j * crossWidth);
            y = j * height;
            addBlock(index++, x, y, crossWidth, height);
        
    

但上面的代码在逻辑还是存在错误,要修改逻辑错误,就必现修改添加方块的算法。

改善算法

根据功能需求,需要9x9大小的方格表的四周和对角线上提交可点击的方块。因此判断一个表格位置是否需要添加方块就是判断该表格是不是处于四周或对角线上,这个判断很容易就能做到。于是有了下面的 version3.0

    private final static int MAX_NUM = 9;

    private void addBlock(int index, int x, int y, int w, int h) 
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
        lp.leftMargin = x;
        lp.topMargin = y;
        TouchView view = new TouchView(mContext);
        view.init(index, TouchView.DISTOGGLED, mDrawables, lp, mToggledListener);
        mFrame.addView(view, lp);
    

    private void showBlock() 
        int maxIndex = MAX_NUM - 1;
        int itemWidth = mScreenWidth / MAX_NUM;
        int itemHeight = mScreenHeight / MAX_NUM;

        int index = 0;
        for (int i = 0; i <= maxIndex; ++i) 
            for (int j = 0; j <= maxIndex; ++j) 
                boolean show =
                    (i == 0 || i == maxIndex || j == 0 || j == maxIndex)
                    || (i == j) || (i == (maxIndex - j));

                if (show) 
                    int x = i * itemWidth;
                    int y = j * itemHeight;
                    int w = itemWidth;
                    int h = itemHeight;
                    if (i == maxIndex) 
                        w = mScreenWidth - maxIndex * itemWidth;
                    
                    if (j == maxIndex) 
                        h = mScreenHeight - maxIndex * itemHeight;
                    

                    addBlock(index++, x, y, w, h);
                
            
        
    

持续重构

到目前为止 version3.0 已经能够正常工作,并且代码逻辑也比较清晰易于阅读。但如果从更宏观的角度来看,还存在重构的空间,比如:现在每一个方块都是一个独立的View,其实可以使用自定义View,通过改写onDraw来自己描绘每一个方块。

以上是关于记录一次重构的主要内容,如果未能解决你的问题,请参考以下文章

记录一次重构

代码重构的实战经验和那些坑

排球计分程序重构

一次业务网关用ASP.NET Core 2.1重构的小结

C#万事开头难<机房重构;

剑指offer-面试题32-分行从上到下打印二叉树-二叉树遍历