从鲍勃叔叔的书清洁代码(例子是Java,所以这是我的第一个java翻译),我一直在玩素数的重构例子。
问题是:您如何重构以下代码?
这里有4个版本:UglyVersion :-),BobVersion,PeteVersion,PeteVersionClassMember。我这样做很有趣,也希望你也喜欢: - )
class ProgramBad
{
static void Main()
{
int[] result = GeneratePrimes.generatePrimes(30);
foreach (var i in result)
Console.Write(i.ToString() + ", ");
}
}
/// <summary>
/// Given an array of integers starting at 2, cross out the multiples of 2. Fine the next
/// uncrossed integer, and cross out all of its multiples.
/// Repeat until you have passed the square root of the maximum value
/// </summary>
public class GeneratePrimes
{
public static int[] generatePrimes(int maxValue)
{
if (maxValue >= 2) // the only valid case
{
// declarations
int s = maxValue + 1; // size of the array
bool[] f = new bool[s];
int i;
// initialize array to be true
for (i = 0; i < s; i++)
{
f[i] = true;
}
// get rid of 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 a 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])
primes[j++] = i;
}
return primes; // return the primes
} else // maxvalue < 2
return new int[0]; // return null array if bad input
}
}
鲍勃的重构版本:
class ProgramBob
{
static void Main()
{
int[] result = PrimeGeneratorBob.generatePrimes(30);
foreach (var i in result)
Console.Write(i + ", ");
}
}
/// <summary>
/// Generates prime number 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 (eg 3 ) integer, and cross out all its multiples (eg 6,9,12,14)
/// Repeat until there are no more multipes in the array
/// </summary>
class PrimeGeneratorBob
{
static bool[] crossedOut;
static int[] result;
static public int[] generatePrimes(int maxValue)
{
if (maxValue < 2)
return new int[0];
else
{
uncrossIntegersUpTo(maxValue);
crossOutMultiples();
putUncrossedIntegersIntoResult();
return result;
}
}
static void uncrossIntegersUpTo(int maxValue)
{
crossedOut = new bool[maxValue + 1]; // as bool array starts at 0, and if maxvalue = 10, we need an array of length 11
for (int i = 2; i < crossedOut.Length; i++) // less than as we don't want to reference crossedOut[4] which doesn't exist
crossedOut[i] = false;
}
static void crossOutMultiples()
{
int limit = determineIterationLimit();
for (int i = 2; i <= limit; i++)
if (notCrossed(i))
crossOutMultiplesOf(i);
}
static int determineIterationLimit()
{
// Every multiple in the array has a prime factor that
// is less than or equal to the square root of the array size,
// which is the largest number we are trying to find primes in.
// So we don't have to cross out numbers
// larger than that square root of the maxnumber, as they will have been crossed out already
double iterationLimit = Math.Sqrt(crossedOut.Length);
return (int) iterationLimit;
}
static void crossOutMultiplesOf(int i)
{
for (int multiple = 2*i; multiple < crossedOut.Length; multiple += i)
crossedOut[multiple] = true;
}
static bool notCrossed(int i)
{
return crossedOut[i] == false;
}
static void putUncrossedIntegersIntoResult()
{
result = new int[numberOfUncrossedIntegers()];
for (int j = 0, i = 2; i < crossedOut.Length; i++)
if (notCrossed(i))
result[j++] = i;
}
static int numberOfUncrossedIntegers()
{
int count = 0;
for (int i = 2; i < crossedOut.Length; i++)
if (notCrossed(i))
count++;
return count;
}
}
我喜欢这个:
周末我与一位朋友配对,我们想出了这个版本:
class PetesRefactored
{
static void MainPete()
{
PrimeGeneratorPete pg = new PrimeGeneratorPete();
int[] arrayOfPrimes = pg.generatePrimes(30);
foreach (int prime in arrayOfPrimes)
Console.Write(prime + ", ");
}
}
/// <summary>
/// Generates prime number 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 (eg 3 ) integer, and cross out all its multiples (eg 6,9,12,14)
/// Repeat until there are no more multipes in the array
/// </summary>
class PrimeGeneratorPete
{
public int[] generatePrimes(int maxValue)
{
bool[] crossedOut;
if (maxValue < 2)
return new int[0];
else
{
crossedOut = new bool[maxValue + 1];
uncrossIntegersUpTo(crossedOut);
crossOutMultiples(crossedOut);
return putUncrossedIntegersIntoResult(crossedOut);
}
}
void uncrossIntegersUpTo(bool[] crossedOut)
{
for (int i = 2; i < crossedOut.Length; i++)
crossedOut[i] = false;
}
void crossOutMultiples(bool[] crossedOut)
{
int limit = determineIterationLimit(crossedOut);
for (int i = 2; i <= limit; i++)
if (!crossedOut[i])
crossOutMultiplesOf(crossedOut, i);
}
int determineIterationLimit(bool[] crossedOut)
{
// Every multiple in the array has a prime factor that
// is less than or equal to the square root of the array size,
// which is the largest number we are trying to find primes in.
// So we don't have to cross out numbers
// larger than that square root of the maxnumber, as they will have been crossed out already
double iterationLimit = Math.Sqrt(crossedOut.Length);
return (int) iterationLimit;
}
void crossOutMultiplesOf(bool[] crossedOut, int i)
{
for (int multiple = 2*i; multiple < crossedOut.Length; multiple += i)
crossedOut[multiple] = true;
}
int[] putUncrossedIntegersIntoResult(bool[] crossedOut)
{
int[] result = new int[numberOfUncrossedIntegers(crossedOut)];
for (int j = 0, i = 2; i < crossedOut.Length; i++)
if (!crossedOut[i])
result[j++] = i;
return result;
}
int numberOfUncrossedIntegers(bool[] crossedOut)
{
int count = 0;
for (int i = 2; i < crossedOut.Length; i++)
if (!crossedOut[i])
count++;
return count;
}
}
我们:
感谢Casey和anon。
以下是使用crossedOut作为类级别变量的代码。我喜欢这个,因为它在将参数传递给方法时减少了一些噪音。
class PrimeGeneratorPeteClassMember
{
bool[] crossedOut;
public int[] generatePrimes(int maxValue)
{
if (maxValue < 2)
return new int[0];
else
{
crossedOut = new bool[maxValue + 1];
uncrossIntegersUpTo();
crossOutMultiples();
return putUncrossedIntegersIntoResult();
}
}
void uncrossIntegersUpTo()
{
for (int i = 2; i < crossedOut.Length; i++)
crossedOut[i] = false;
}
void crossOutMultiples()
{
int limit = determineIterationLimit();
for (int i = 2; i <= limit; i++)
if (!crossedOut[i])
crossOutMultiplesOf(i);
}
int determineIterationLimit()
{
double iterationLimit = Math.Sqrt(crossedOut.Length);
return (int)iterationLimit;
}
void crossOutMultiplesOf(int i)
{
for (int multiple = 2 * i; multiple < crossedOut.Length; multiple += i)
crossedOut[multiple] = true;
}
int[] putUncrossedIntegersIntoResult()
{
int[] result = new int[numberOfUncrossedIntegers()];
for (int j = 0, i = 2; i < crossedOut.Length; i++)
if (!crossedOut[i])
result[j++] = i;
return result;
}
int numberOfUncrossedIntegers()
{
int count = 0;
for (int i = 2; i < crossedOut.Length; i++)
if (!crossedOut[i])
count++;
return count;
}
}
答案 0 :(得分:4)
诚实?我根本不会重构。也许这只是因为我曾经是一个数学知识,但我发现第一个版本更容易阅读。
重构算法通常没有多大意义。当您重构代码时,意味着您希望重用或更改它的一部分。在这种情况下,整个代码块是静态的和不可变的 - 您可能更改的唯一方法是使用不同的算法替换整个函数。像notCrossed
这样的单行函数似乎特别无用;它们只是用来使代码更加冗长,而无需解释任何尚不明显的内容。
实际上,也许我会做两次重构:
将您的班级名称GeneratePrimes
更改为PrimeGenerator
,就像您已经做的那样。动词类名称总是让我循环 - 它是一个类还是一个方法?
将其更改为返回IList<int>
或IEnumerable<int>
。返回数组类型为Considered Harmful。
编辑 - 再进行三次重构将删除一些无用的注释,并改为使用有意义的变量名称(如果适用)。
除此之外 - 坚持使用原始版本!
编辑 - 实际上,我越看原文,就越不喜欢它。不是因为它的组织方式,而是因为它的编写方式。这是我的重写:
public class PrimeGenerator
{
public static IEnumerable<int> GeneratePrimes(int maxValue)
{
if (maxValue < 2)
return Enumerable.Empty<int>();
bool[] primes = new bool[maxValue + 1];
for (int i = 2; i <= maxValue; i++)
primes[i] = true;
for (int i = 2; i < Math.Sqrt(maxValue + 1) + 1; i++)
{
if (primes[i])
{
for (int j = 2 * i; j <= maxValue; j += i)
primes[j] = false;
}
}
return Enumerable.Range(2, maxValue - 1).Where(i => primes[i]);
}
}
那里好多了!
答案 1 :(得分:1)
只是澄清麦克斯韦尔倒IF的评论:
这很糟糕:
if (maxValue >= 2)
{ blah }
else
return new int[];
这很好
if (maxValue < 2)
return new int[0];
blah
答案 2 :(得分:0)
我唯一能做的就是在你的最终版本中将crosOut布尔数组作为类成员。其余的方法都是私有的,所以通过将数组传入和传出每个方法都不会有太大的好处。
答案 3 :(得分:0)
使用LINQ的优雅解决方案:
static IEnumerable<int> PrimeNumbers(int maxValue)
{
if (maxValue > 1 && maxValue < int.MaxValue)
{
var integers = Enumerable.Range(2, maxValue);
for (;;)
{
int item = integers.FirstOrDefault();
if (item == 0)
{
break;
}
yield return item;
integers = integers.Where(x => x % item != 0);
}
}
}