情势与施行lehu娱乐手机平台网站

第5章 重构

  在马丁Fowler的力作《重构》1书中,他把重构定义为:“在不更动代码外在表现的前提下对对代码做出修改,以革新代码内部结构的进度。”不过我们为啥要更上1层楼已经能够工作的代码结构吧?大家不是都领悟“假如它从不坏,就绝不去修复它!”吗?

  每3个软件模块都有叁项职务。第1个职务是它运营起来所产生的作用。那也是该模块得以存在的缘故。第二个职务是它要应对的变通。大约全数的模块在它们的生命周期中都要调换,开垦者有义务保证那种更动应竭尽地质大学致。三个麻烦改换的模块是有标题标,尽管能够工作,也须要对它实行核对。第多个职责是要能够使其阅读者明白。对该模块不熟悉的开采人士能够相比便于地翻阅并驾驭它。四个十分的小概被通晓的模块也是不不荒谬的,同样必要对它实行勘误。

  如何才干让软件易于阅读、易于修改呢?本书的主要性内容都是有关部分原则和形式的,那些规则和情势紧要对象正是为了帮助您制造出越来越灵活和全数实用性的软件模块。可是,要使软件模块易于阅读和改动,所急需的不只是1对口径和情势。还索要您的集中力,需求纪律约束,须要创立美的Haoqing。

伍.1 素数发生程序:3个回顾的重构示例

  观察如下代码,那么些顺序会时有发生素数。它是二个大函数,当中有赞助阅读的注释和数不尽单字母变量。

/// <remark>
/// This class Generates prime numbers up to a user specified
/// maximum. The algorithm used is the Sieve of Eratosthenes.
///
/// Eratosthenes of Cyrene, b. c. 276 BC, Cyrene, Libya --
/// d. c. 194, Alexandria. The first man to calculate the
/// circumference of the Earth. Also known for working on
/// calendars with leap years and ran the library at
/// Alexandria.
///
/// The algorithm is quite simple. Given an array of integers
/// starting at 2. Cross out all multiples of 2. Find the
/// next uncrossed integer, and cross out all of its multiples.
/// Repeat until you have passed the square root of the
/// maximum value.
///
/// Written by Robert C. Martin on 9 Dec 1999 in Java
/// Translated to C# by Micah Martin on 12 Jan 2005.
///</remark>
using System;
/// <summary>
/// author: Robert C. Martin
/// </summary>
public class GeneratePrimes
{
    ///<summary>
    /// Generates an array of prime numbers.
    ///</summary>
    ///
    /// <param name="maxValue">The generation limit.</param>
    public static int[] GeneratePrimeNumbers(int maxValue)
    {
        if (maxValue >= 2) // the only valid case
        {
            // declarations
            int s = maxValue + 1; // size of array
            bool[] f = new bool[s];
            int i;
            // initialize array to true.
            for (i = 0; i < s; i++)
                f[i] = true;
            // get rid of known non-primes
            f[0] = f[1] = false;
            // sieve
            int j;
            for (i = 2; i < Math.Sqrt(s) + 1; i++)
            {
                if (f[i]) // if i is uncrossed, cross its multiples.
                {
                    for (j = 2 * i; j < s; j += i)
                        f[j] = false; // multiple is not prime
                }
            }
            // how many primes are there?
            int count = 0;
            for (i = 0; i < s; i++)
            {
                if (f[i])
                    count++; // bump count.
            }
            int[] primes = new int[count];
            // move the primes into the result
            for (i = 0, j = 0; i < s; i++)
            {
                if (f[i]) // if prime
                    primes[j++] = i;
            }
            return primes; // return the primes
        }
        else // maxValue < 2
            return new int[0]; // return null array if bad input.
    }
}

 

伍.一.一 单元测试

  为GeneratePrimes编写单元测试:

using NUnit.Framework;
[TestFixture]
public class GeneratePrimesTest
{
    [Test]
    public void TestPrimes()
    {
        int[] nullArray = GeneratePrimes.GeneratePrimeNumbers(0);
        Assert.AreEqual(nullArray.Length, 0);
        int[] minArray = GeneratePrimes.GeneratePrimeNumbers(2);
        Assert.AreEqual(minArray.Length, 1);
        Assert.AreEqual(minArray[0], 2);
        int[] threeArray = GeneratePrimes.GeneratePrimeNumbers(3);
        Assert.AreEqual(threeArray.Length, 2);
        Assert.AreEqual(threeArray[0], 2);
        Assert.AreEqual(threeArray[1], 3);
        int[] centArray = GeneratePrimes.GeneratePrimeNumbers(100);
        Assert.AreEqual(centArray.Length, 25);
        Assert.AreEqual(centArray[24], 97);
    }
}

 

它才用了1种计算学的主意,检查实验发生器能够爆发0,二,3以及十0以内的素数。第3种情景下,应该未有素数。在其次中状态下,应该有二个素数,并且该素数是二。在第二种情况下,应该有八个素数,它们应该是2和三.说起底一种状态相应有2五个素数,个中最终二个应有是九7.即便持有的那些测试都经过了,那么就感觉发生器是足以干活的。小编困惑那种做法的可信性,不过自个儿不能够设想出多个成立的景色,在那几个情形下那一个测试都将经过不过函数却是错误的。

5.1.2 重构

  为了推进重构程序,作者利用了涵盖ReSharper重构附属类小部件(来自JetBrains)的Visual
Studio。使用该工具得以非凡轻巧地领取方法及重命名变量和类。

  鲜明,主函数分成了1个独立函数。第3个函数对具备的变量进行开始化,并做好过滤所需的预备工作;第三个函数施行过滤工作;第三个函数把过滤结果存放在叁个整型数组中。为了更请示地球表面现那么些结构,作者把这个函数提抽出来放在二个分其余章程中。作者还去掉了一些不须求的批注,并且把类名改造为PrimeGenerator。改动后的代码仍旧能因此装有测试。

  对那贰个函数的领取迫使本身把该函数的壹对局地变量升高为类的静态域。那更明了地方统一标准明了哪些是有的变量,哪个变量的影响更普及。代码如下:

///<remark>
/// This class Generates prime numbers up to a user specified
/// maximum. The algorithm used is the Sieve of Eratosthenes.
/// Given an array of integers starting at 2:
/// Find the first uncrossed integer, and cross out all its
/// multiples. Repeat until there are no more multiples
/// in the array.
///</remark>
using System;
public class PrimeGenerator
{
    private static int s;
    private static bool[] f;
    private static int[] primes;
    public static int[] GeneratePrimeNumbers(int maxValue)
    {
        if (maxValue < 2)
            return new int[0];
        else
        {
            InitializeSieve(maxValue);
            Sieve();
            LoadPrimes();
            return primes; // return the primes
        }
    }
    private static void LoadPrimes()
    {
        int i;
        int j;
        // how many primes are there?
        int count = 0;
        for (i = 0; i < s; i++)
        {
            if (f[i])
                count++; // bump count.
        }
        primes = new int[count];
        // move the primes into the result
        for (i = 0, j = 0; i < s; i++)
        {
            if (f[i]) // if prime
                primes[j++] = i;
        }
    }
    private static void Sieve()
    {
        int i;
        int j;
        for (i = 2; i < Math.Sqrt(s) + 1; i++)
        {
            if (f[i]) // if i is uncrossed, cross its multiples.
            {
                for (j = 2 * i; j < s; j += i)
                    f[j] = false; // multiple is not prime
            }
        }
    }
    private static void InitializeSieve(int maxValue)
    {
        // declarations
        s = maxValue + 1; // size of array
        f = new bool[s];
        int i;
        // initialize array to true.
        for (i = 0; i < s; i++)
            f[i] = true;
        // get rid of known non-primes
        f[0] = f[1] = false;
    }
}

 

  InitializeSieve函数有一部分糊涂,全体笔者对它实行了一定大的调动。先把全体变量s的地点替换为f.Length。然互,改动了1个函数名字,使它们更具表明力。最终重新安排了InitializeArrayOfIntegers(也正是本来的InitializeSieve)的内部结构,使它更易于阅读。最终代码仍旧通过装有的测试。代码如下:

    public class PrimeGenerator
    {
        private static bool[] f;
        private static int[] result;
        public static int[] GeneratePrimeNumbers(int maxValue)
        {
            if (maxValue < 2)
                return new int[0];
            else
            {
                InitializeArrayOfIntegers(maxValue);
                CrossOutMultiples();
                PutUncrossedIntegersIntoResult();
                return result;
            }
        }
        private static void InitializeArrayOfIntegers(int maxValue)
        {
            // declarations
            f = new bool[maxValue + 1];
            f[0] = f[1] = false; //neither primes nor multiples.
            for (int i = 2; i < f.Length; i++)
                f[i] = true;
        }
    }

 

  下一步,来探视克罗丝OutMultiples。那些函数和别的1些函数中有那2个形如if(f[i]==true)的言辞。那条语句的意向是检查i是还是不是未有被筛选过,全体笔者把f改名称叫un克罗丝ed。可是改名后发出了像un克罗斯ed[i]=false那样难看的讲话。小编意识双重否定会令人吸引。所以把数组更名称为is克罗丝ed,并且改换了独具布尔值的含义。

  小编去掉了is克罗斯ed[lehu娱乐手机平台网站,0]和isCrossed[1]为true的初步化语句,并确定保证函数中的全部片段都不会采用小于二的目录访问isCrossed数组。作者领到了克罗斯OutMultiples函数内部循环部分,把它取名称叫CrossOutMultiplesOf。同样,作者感到if(is克罗斯ed[i]==false)也会令人吸引,所以创制了三个名称叫Not克罗丝ed的函数,把原来if语句更换为if(Not克罗丝ed(i)).

  作者在一个疏解上花了某个时辰,那几个注释试图解释为什么只须求变量至数首席施行官度的平方根。那指点作者把总计部分提取成一个函数,个中能够停放表达性的注脚。

代码如下:

public class PrimeGenerator
{
    private static bool[] isCrossed;
    private static int[] result;
    public static int[] GeneratePrimeNumbers(int maxValue)
    {
        if (maxValue < 2)
            return new int[0];
        else
        {
            InitializeArrayOfIntegers(maxValue);
            CrossOutMultiples();
            PutUncrossedIntegersIntoResult();
            return result;
        }
    }
    private static void InitializeArrayOfIntegers(int maxValue)
    {
        isCrossed = new bool[maxValue + 1];
        for (int i = 2; i < isCrossed.Length; i++)
            isCrossed[i] = false;
    }
    private static void CrossOutMultiples()
    {
        int maxPrimeFactor = CalcMaxPrimeFactor();
        for (int i = 2; i < maxPrimeFactor + 1; i++)
        {
            if (NotCrossed(i))
                CrossOutputMultiplesOf(i);
        }
    }
    private static int CalcMaxPrimeFactor()
    {
        // We cross out all multiples of p, where p is prime.
        // Thus, all crossed out multiples have p and q for
        // factors. If p > sqrt of the size of the array, then
        // q will never be greater than 1. Thus p is the
        // largest prime factor in the array and is also
        // the iteration limit.
        double maxPrimeFactor = Math.Sqrt(isCrossed.Length) + 1;
        return (int)maxPrimeFactor;
    }
    private static void CrossOutputMultiplesOf(int i)
    {
        for (int multiple = 2 * i;
        multiple < isCrossed.Length;
        multiple += i)
            isCrossed[multiple] = true;
    }
    private static bool NotCrossed(int i)
    {
        return isCrossed[i] == false;
    }
}

 

  最终三个要重构的函数是PutUncrossedIntegersIntoResult。这么些函数有两片段效用。第一部分总结了数组中尚无过滤掉的整数数目,并创制了二个一致大小的数组来存放在那个结果。第3有的把那一个并未有过滤掉的整数搬移到结果数组中。我把第一片段效益抽取来,放到它和谐的函数中,并做了此外部分清监护人业。代码如下:

    private static void PutUncrossedIntegersIntoResult()
    {
        result = new int[NumberOfUncrossedIntegers()];
        for (int j = 0, i = 2; i < isCrossed.Length; i++)
        {
            if (NotCrossed(i))
                result[j++] = i;
        }
    }
    private static int NumberOfUncrossedIntegers()
    {
        int count = 0;
        for (int i = 2; i < isCrossed.Length; i++)
        {
            if (NotCrossed(i))
                count++; // bump count.
        }
        return count;
    }

 

5.一.3 最终审视

  接着,作者对整个程序做了最后的审美,从头到尾阅读了三回,差不多像阅读集合申明,那是非常关键的一步。迄今停止,我们重构的都以代码片段,未来要看那些片段结合在一同是不是是一个颇具可读性的完好。

  作者把一部分不合乎的名字给改掉。去掉了并未要求的“+一”。由于忧虑一些边角的地方未有测试到,所以其它编写了测试,用来检查二~500所爆发的素数列表中一直不倍数存在。

重构最后版如下:

///<remark>
/// This class Generates prime numbers up to a user specified
/// maximum. The algorithm used is the Sieve of Eratosthenes.
/// Given an array of integers starting at 2:
/// Find the first uncrossed integer, and cross out all its
/// multiples. Repeat until there are no more multiples
/// in the array.
///</remark>
using System;
public class PrimeGenerator
{
    private static bool[] crossedOut;
    private static int[] result;
    public static int[] GeneratePrimeNumbers(int maxValue)
    {
        if (maxValue < 2)
            return new int[0];
        else
        {
            UncrossIntegersUpTo(maxValue);
            CrossOutMultiples();
            PutUncrossedIntegersIntoResult();
            return result;
        }
    }
    private static void UncrossIntegersUpTo(int maxValue)
    {
        crossedOut = new bool[maxValue + 1];
        for (int i = 2; i < crossedOut.Length; i++)
            crossedOut[i] = false;
    }
    private static void PutUncrossedIntegersIntoResult()
    {
        result = new int[NumberOfUncrossedIntegers()];
        for (int j = 0, i = 2; i < crossedOut.Length; i++)
        {
            if (NotCrossed(i))
                result[j++] = i;
        }
    }
    private static int NumberOfUncrossedIntegers()
    {
        int count = 0;
        for (int i = 2; i < crossedOut.Length; i++)
        {
            if (NotCrossed(i))
                count++; // bump count.
        }
        return count;
    }
    private static void CrossOutMultiples()
    {
        int limit = DetermineIterationLimit();
        for (int i = 2; i <= limit; i++)
        {
            if (NotCrossed(i))
                CrossOutputMultiplesOf(i);
        }
    }
    private static int DetermineIterationLimit()
    {
        // Every multiple in the array has a prime factor that
        // is less than or equal to the root of the array size,
        // so we don't have to cross off multiples of numbers
        // larger than that root.
        double iterationLimit = Math.Sqrt(crossedOut.Length);
        return (int)iterationLimit;
    }
    private static void CrossOutputMultiplesOf(int i)
    {
        for (int multiple = 2 * i;
        multiple < crossedOut.Length;
        multiple += i)
            crossedOut[multiple] = true;
    }
    private static bool NotCrossed(int i)
    {
        return crossedOut[i] == false;
    }
}

 

测试代码:

using NUnit.Framework;
[TestFixture]
public class GeneratePrimesTest
{
    [Test]
    public void TestPrimes()
    {
        int[] nullArray = PrimeGenerator.GeneratePrimeNumbers(0);
        Assert.AreEqual(nullArray.Length, 0);
        int[] minArray = PrimeGenerator.GeneratePrimeNumbers(2);
        Assert.AreEqual(minArray.Length, 1);
        Assert.AreEqual(minArray[0], 2);
        int[] threeArray = PrimeGenerator.GeneratePrimeNumbers(3);
        Assert.AreEqual(threeArray.Length, 2);
        Assert.AreEqual(threeArray[0], 2);
        Assert.AreEqual(threeArray[1], 3);
        int[] centArray = PrimeGenerator.GeneratePrimeNumbers(100);
        Assert.AreEqual(centArray.Length, 25);
        Assert.AreEqual(centArray[24], 97);
    }
    [Test]
    public void TestExhaustive()
    {
        for (int i = 2; i < 500; i++)
            VerifyPrimeList(PrimeGenerator.GeneratePrimeNumbers(i));
    }
    private void VerifyPrimeList(int[] list)
    {
        for (int i = 0; i < list.Length; i++)
            VerifyPrime(list[i]);
    }
    private void VerifyPrime(int n)
    {
        for (int factor = 2; factor < n; factor++)
            Assert.IsTrue(n % factor != 0);
    }
}

 

 

结论

  重构后的主次读起来比一早先要好广大。程序办事也更加好一些。程序变的得轻巧掌握,由此也更易于创新。并且,程序结构的相继部分之间互相隔绝,那同一也使它更易于改换。

  你恐怕担忧提抽出单纯调用2遍的函数会对质量变成负面包车型地铁熏陶。作者以为在大多数景观下,提抽取主意所充实的可读性是值得花额外的部分微薄开支的。但是,大概那么些异常的小的支付存在于很深的内循环中,这样会形成较大的习性损耗。作者建议1旦那种损耗是能够忽略的,等待现在再去表明那种要是是谬误的。

  那值得我们耗时吗?毕竟,程序已经足以成功所需的意义。作者强烈推荐你平日对你所编纂和爱慕的每三个模块进行那种重构实行。所投入的年月和随之为和谐和客人节省的用力对待起来是卓殊少的。

  重构的指标是为着天天、每时、每分都干净你的代码。大家不想让脏乱积累。大家想经过最小的鼎力就可见对大家的连串开始展览扩充和改动。要想具有这种手艺,最要害的便是保持代码整洁。

  关于那或多或少,笔者怎么强调都不过分。本书中的全体标准和形式对于水污染的代码来讲将从未此外价值。在学习标准和情势前,首先学习编写清洁的代码。

 

 

摘自:《敏捷软件开拓:原则、情势与实行(C#版)》Robert C.Martin    Micah
Martin 著

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图