内裤外穿——错位及不伦不类

      内裤这种东西是穿在外裤里面的,穿在外面就成了一种“错位”。错位的结局必然是非驴非马、不伦不类。不要以为这种事情不会在代码中发生,代码中同样可能存在错位和不伦不类。
题目:用递归方法求n!。

#include <stdio.h>
int main()
{
int fac(int n);
int n;
int y;
printf(
"input an integer number:");
scanf(
"%d",&n);
y
=fac(n);
printf(
"%d!=%d\n",n,y);
return0;
}
int fac(int n)
{
int f;
if(n<0)
printf(
"n<0,data error!");
elseif(n==0||n==1)
f
=1;
else f=fac(n-1)*n;
return(f);
}

————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p188


      这段代码,当用户输入一个负整数后,main()函数会把这个错误的数据传给fac()函数,fac()函数会在输出设备上输出“n<0,data error!”,然而自相矛盾的是fac()函数还会把一个不确定的垃圾值错误地返回给main()函数,然而main()并不知道这是个错误的返回值,因而“很傻很天真”地输出这个毫无意义的返回值。最后的运行结果类似下面这样:
input an integer number:-1
n<0,data error!-1!=-1
      一方面它说数据错误,但同时又告诉你“-1!=-1”。显然,这是一个不伦不类的结果。和内裤外裤同时暴露有得一拼。
      然而,仅仅揭露这种荒谬是远远不够的,更重要的事情在于如何避免这种荒谬。而想避免这种荒谬就必须揭示产生这种荒谬的原因。
      先考察一下样本代码的思路。main()函数主要由三个部分组成:输入n;计算n!;输出。而fac()函数的想法是如果n是负数,无法计算,输出“data error!”,否则如果n为0或1则f=1;否则计算“f=fac(n-1)*n”;最后返回f值。
      问题就出在这里,不难发现fac()这个函数的功能几乎根本无法总结并描述,即使描述出来也是错误的,因为如果n是负数的话它最后同样也返回一个值。这种函数的功能是错乱的,造成这种错乱的原因在于,在构思main()的时候没有想到不是所有的整数都有阶乘(高等数学那么多存在性定理算是白学了),但是在写fac()又突然想到了,然而却不是回头重新考虑程序的总体思路,而是匆忙地把对负数的处理写在了fac()中,然而对负数的处理本应该在main()中进行,把这个处理写在fac()中就人为地制造出了一种“错位”,这就是结果不伦不类的根本原因。
      造成错位的另一种可能性是,事先根本就没有总体的思路(main()),在main()写到一半时去写fac(),在写fac()想到了对负数的处理问题于是顺手写出,写完fac()之后再回到main()继续写完剩余部分的代码。这种缺乏总体构思东一榔头西一棒子写到哪算哪的写代码方法,完全违背了结构化程序设计自顶向下的思想,写出漏洞百出的程序是顺理成章的结局。
      按照自顶向下的技术风格则不会产生这种问题。自顶向下要求首先构思main()函数:

int main( void )
{
  int n;
  //输入n
  //计算n!
  //输出n! 
   
   return 0;
}

      训练有素的程序员应该能看出这个总体思路的逻辑毛病,因为计算n!的前提是n!存在且能够计算n!(n比较大时就完全成了另一个问题,这个问题这里不打算讨论。这里假设n不是很大,n!可用简单的办法求得)。历史上,人类曾花了2000多年的时间研究用尺规三等分角的方法,可最后才发现这种方法根本不存在,这个教训可谓深矣。如果不理解计算的前提是可以计算,不管学过多少数学,都算白学。
      当然,无论是谁,思虑不周都是可能的。最初没想到而后来想到也不算是罪大恶极。但问题是后来一旦想到了就一定要返回main()重新审视总体构思,否则一旦顶层存在问题,代码再怎么写也是错的。俗话说,上梁不正下梁歪。人们往往能看到社会风气的败坏,但却很少思考败坏的源头何在,这样是解决不了问题的。所以,在这种情况下必须重新修正main()函数,才能继续后面的工作,而绝不能急着完成代码细节。

int main( void )
{
  int n;
  //输入n
  if ( n < 0 )
{//错误处理
}
  else 
{
     //计算n!
     // 输出n!  
}
   
   return 0;
}

      总体构思无误后,才有可能对fac()提出正确的功能要求。fac()只需要针对可计算的整数求阶乘并返回这个值,显然fac()的形参应该是unsigned类型,返回值也以unsigned类型为好,因为计算范围比返回值为int的更大些。把这些考虑用代码表达出来就是:

unsigned fac(unsigned);
int main( void )
{
  int n;
  //输入n
  if ( n < 0 )
{//错误处理
}
  else 
{
     //计算n!
     // 输出n!  
}
   
   return 0;
}
//fac():计算n!并返回
unsigned fac(unsigned n)
{
}

      到了这一步,后面的事情就是简单的力气活了。

#include <stdio.h>

unsigned fac(unsigned);

int main( void )
{
  int n;

  //输入n
  printf("请输入一个正整数:");
  scanf("%d",&n);

  if ( n < 0 )
    printf("该数小于0,无法计算!\n");            //错误处理
  else 
    printf("%d!=%u\n" , n , fac( (unsigned)n ) );//计算,输出n!

   return 0;
}

//fac():计算n!并返回
unsigned fac(unsigned n)
{
   if ( n == 0U )
      return 1U ;
   else
      return n * fac( n - 1U ) ;
}

总结:
1.结构化程序设计的一个核心理念就是层次,自顶向下必须先建立层次这个概念才可能实现。
2.自顶向下就是由高层到低层,先粗后细,先大节后小节。切忌在各个不同的层次间玩“穿越”,否则就会导致层次错位,就如同把内裤套在外裤外面,结果必然不伦不类。
3.修改程序也必须遵循这样的次序,上层的问题切莫企图在下层修补;如果是上层存在问题,下层无论怎样忙活都无济于事。就如同老板弱智,员工勤恳一样,只会认认真真地把事情弄糟而已。

原文地址:https://www.cnblogs.com/pmer/p/2173183.html