境界テスト-BUGを現す
7268 ワード
境界テスト-BUGを現す
タイトル:関数を書いて、1行の文字を入力して、この文字列の中で最も長い単語を出力します.
#include <stdio.h>
#include <string.h>
int main()
{int alphabetic(char);
int longest(char []);
int i;
char line[100];
printf("input one line:
");
gets(line);
printf("The longest word is:");
for(i=longest(line);alphabetic(line[i]);i++)
printf("%c",line[i]);
printf("
");
return 0;
}
int alphabetic(char c)
{
if((c>='a'&&c<='z')||(c>='A'&&c<='z'))
return(1);
else
return(0);
}
int longest(char string[])
{int len=0,i,length=0,flag=1,place=0,point;
for(i=0;i<=strlen(string);i++)
if(alphabetic(string[i]))
if(flag)
{point=i;
flag=0;
}
else
len++;
else
{flag=1;
if(len>=length)
{length=len;
place=point;
len=0;
}
}
return(place);
}
実行結果:
input a line: I am a student. The longest word is : student
タイトルは「関数を書き、1行の文字を入力し、この文字列の中で最も長い単語を出力する」ことを要求しているが、alphabetic()関数でもlongest()関数でも「1行の文字を入力し、この文字列の中で最も長い単語を出力する」という機能要求は実現されていない.長い間疑問に思っていたが、この機能を実現する関数がmain()であることに気づいた.これは人を笑わせるのは避けられない.従来の慣例では、main()を書く必要はありませんが、main()が「関数」ではないとは言えません.しかしmain()が完了することを要求する場合、通常は完全な問題として提出され、「関数を書く」という要求は提出されません.「関数を1つ書く」と強弁してもmain()を書くことは排除されず、強引な強弁に近い.しかし、もし本当にこのように口が堅い人がいたら、あなたは本当に彼を困らせます.
棺を見ずに涙を流さない以上、下を見続けてもいい.
コードにflagという変数が一目で見られた.経験によると、このflag変数のあるコードは、80%以上がゴミコードであることが明らかになった.道理は簡単です:まず、多くの問題はこのねじれマークの変数を設定する必要はありません.自分の思考を腐った麻花のように歪めるのが上手な人だけがflagというぼろぼろの法宝を時々祭るのが好きです.次に、標準変数を設定する必要があっても、優れたコード作成者は、この意味の曖昧な名前をフラグ変数名として使用するのではなく、より適切で、意味がより明確で、問題を記述するのに適した名前を使用します.したがって、一般的にflagはコードのゴミ度を反映することが多い.
ごみコードについては,あまり細かく分析する必要はなく,誤りを指摘すればよい.このようなコードの構想を理解しようとしないでください.このようなコードの構想はもともと錯乱しているので、狂人のでたらめを理解しようとしないのと同じです.むやみに建てられたぼろぼろの危険な家を修繕しようとしないで、やり直すのが賢明な選択だ.
しかし、プログラムの脆弱性やエラーを見つけることは、プログラムを完了するよりもずっと難しいことが多い.また、ゴミのコードが間違っているほど、ゴミコードも良好なテスト性を備えていないことが多いからです.
しかし、このようなテスト性の極めて悪いゴミコードに対処するには、境界検査などの簡単な方法が非常に効果的であることが多い.訓練されたプログラマーは、コードを書くときもチェックするときも、境界に特に注意します.彼らはここが間違いやすいことを知っていて、往々にして誤りの千里を失うことが多いからだ.しかし、ゴミコードの著者は、コードが東綴西補、むやみに組み合わせられているため、これらを顧みないか、考慮できないことが多いため、ゴミコードは「境界検査」というナイフに簡単に突き破られやすい.Alphabetic()関数を例にとると,if文に要求される式である(c>='a'&&c<='z')|(c>='A'&&c<='z')を簡単に考察すれば,c<='z'というサブ式がc<='Z'の誤りであることは容易である.これにより,元のコードにBUGが存在することを十分に説明する.
ちなみに、alphabetic()関数のif-else文は非常に愚かです.(c>='a'&&c<='z')|(c>='A'&&c<='Z')という式の値自体は0か1しかないので、この式の値をそのまま返すといいです.ズボンを脱いでおならをする必要はありませんif-else文を書きます.
int
alphabetic(
char
c)
{
return
(c>=
'a'
&&c<=
'z'
)
|| (c>=
'A'
&&c<=
'Z'
);
}
もしかすると、これは簡単な書き間違いや印刷間違いだと思っている人もいるかもしれませんが、この間違いを修正した元のコードは正しいです.では、この間違いを修正してから、簡単な境界テストを運用します.
問題では、1行の文字の中で最も長い単語を出力する必要がありますが、1行の文字の中には0個の単語、1個の単語、2個の単語がある可能性があります.......ここで0単語の場合は境界状況であり、このプログラムを実行して0単語を入力する(コード作成者が連続したいくつかのアルファベット文字を1つの単語としているため、1行のアルファベットを含まない文字を入力する)と、実行時にプログラムが崩れてしまうことに注意してください.この結果は,元のコードが誤りであることを絶対に十分に説明できる.
この結果はどのようにして生まれたのでしょうか.紙で調べてみると、アルファベットを含まない文字を1行入力すると、longest()関数にfor文の内部にネストされたif文が意味なく繰り返し実行されることがわかります
{flag=1;
if(len>=length)
{length=len;
place=point;
len=0;
}
}
一部、placeに付与されたポイントは意外にも不確定なゴミ値です.
この問題のコードをどのように正確に与えるべきですか?問題を正しく解決する前提は、問題を正しく提起することである.そもそも問題の言い方自体には、正しくないところや厳密でないところがたくさんあります.たとえば、「この文字列の中で最も長い単語を出力する」という要求自体は、不明確ではなく似ているように見えます.たとえば、文字列に2つの単語が同じ長さで他の単語より長い場合、この2つの単語のいずれかを出力するべきですか、それとも同時に出力する必要がありますか.また、関数に「1行文字入力」を要求するのもつまらない.問題を正しく解決するためには、元の問題の誤った要求を以下のように訂正する必要がある.
文字列のいずれかの長さが最も長い単語を出力する関数を書きます.ここで単語とは、空白文字を含まない連続文字列をいう.
#include