数週間前にソフトウェアの職人技のイベントに参加しましたが、コメントの1つは「私たちが不正なコードを見ると、誰もがそれを認識していると確信しています」であり、誰もそれ以上の議論なしに賢くうなずきました。
誰もが平均以上のドライバーだと思っているその自明があるので、この種のことはいつも私を心配します。私は悪いコードを認識できると思いますが、他の人がコードのにおいだと考えるものについてもっと知りたいと思っています。それは、人々のブログやほんの一握りの本でしか詳細に議論されていないためです。特に、ある言語ではコードのにおいがし、別の言語ではないという話を聞くと面白いと思います。
私は簡単なものから始めます:
コメントアウトされたコードの割合が高いソース管理のコード-なぜそこにあるのですか?削除するつもりでしたか?半分完成した作品ですか?おそらくそれはコメントアウトされるべきではなく、誰かが何かをテストしていたときにだけ行われたのですか?個人的に、あちこちに奇妙な行があっても、この種のことは本当に迷惑ですが、大きなブロックがコードの残りの部分に散在しているのを見ると、まったく受け入れられません。また、通常、残りのコードも疑わしい品質である可能性が高いことを示しています。
/* Fuck this error */
通常、ナンセンスtry..catch
ブロック、私の注意を引く傾向があります。ちょうど約/* Not sure what this does, but removing it breaks the build */
。
さらにいくつか:
if
ステートメントprocess
、data
、change
、rework
、modify
の関数私が見つけたもの:
/* Stupid database */
$conn = null;
while(!$conn) {
$conn = mysql_connect("localhost", "root", "[pass removed]");
}
/* Finally! */
echo("Connected successfully.");
そうです、MySQL接続をブルートフォースにする必要があるからです。データベースが接続数に問題があったため、タイムアウトが続くことがわかりました。これをデバッグする代わりに、うまくいくまで何度も何度も試してみました。
私にとっての主要な赤信号は、コードブロックの重複です。これは、その人がプログラミングの基礎を理解していないか、怖がって既存のコードベースに適切な変更を加えることができないことを示しています。
私は以前はコメントの欠如を危険信号として数えていましたが、最近、コメントなしで多くの非常に優れたコードに取り組んだので、それを緩和しました。
実際の価値はないという事実にもかかわらず、プログラマーの賢さを自慢しようとするコード:
x ^= y ^= x ^= y;
20,000(誇張)ライン関数。 2つ以上の画面を使用する関数では、リファクタリングが必要です。
同じように、永遠に続くように見えるクラスファイル。それらがすべて内部メソッドでない限り、元のクラスの目的と機能、およびおそらくそれが使用されている場所を明確にするクラスに抽象化できるいくつかの概念があるでしょう。
非説明的、非自明な変数、または多すぎる非自明な非説明的変数。これらは、実際に何が起こっているのかを推測します。
{ Let it Leak, people have good enough computers to cope these days }
さらに悪いことに、それは商用ライブラリからのものです!
英語のコンパイラーがあったとしても、完全にコンパイルして実行できるほど冗長なコメントですが、コードが記述していないことについては何も説明していません。
//Copy the value of y to temp.
temp = y;
//Copy the value of x to y.
y = x;
//Copy the value of temp to x.
x = temp;
また、コードがいくつかの基本的なガイドラインに準拠していれば、廃止された可能性のあるコードへのコメント:
//Set the age of the user to 13.
a = 13;
コンパイル時に警告を生成するコード。
説明的な名前の代わりに名前に数字が含まれている関数のように:
void doSomething()
{
}
void doSomething2()
{
}
関数名を意味のあるものにしてください! doSomethingとdoSomething2が同様のことを行う場合は、違いを区別する関数名を使用します。 doSomething2がdoSomethingからの機能の分離である場合は、その機能の名前を付けます。
Magic Numbers またはMagic Strings。
if (accountBalance>200) { sendInvoice(...); }
salesPrice *= 0.9; //apply discount
if (invoiceStatus=="Overdue") { reportToCreditAgency(); }
最悪ではないかもしれませんが、実装者レベルを明確に示しています。
if(something == true)
言語にforループまたはイテレーター構造がある場合、whileループを使用すると、言語に対する理解の実装者レベルも示されます。
count = 0;
while(count < items.size()){
do stuff
count ++;
}
for(i = 0; i < items.size(); i++){
do stuff
}
//Sure this is not terrible but use the language the way it was meant to be used.
ドキュメンテーション/コメントの悪いスペル/文法は、コード自体とほとんど同じくらい私を食べます。この理由は、コードは人間が読み取り、マシンを実行するためのものであったためです。これが、私たちが高級言語を使用している理由です。もしあなたのドキュメンテーションを理解するのが難しい場合、私はそれを見ることなくコードベースの否定的な意見を先制的に形成させます。
私がすぐに気づくのは深くネストされたコードブロックの頻度です(もしあれば、whileなど)。コードが頻繁に2レベルまたは3レベル以上の深さである場合、それは設計/ロジックの問題の兆候です。そして、それが8つの巣の深さのようになった場合、それが分割されないための非常に良い理由があるはずです。
学生のプログラムを採点するとき、「まばたき」スタイルの瞬間に時々話すことができます。これらは即座の手がかりです:
私の第一印象が正しくないことはめったにありません、そしてこれらの警告ベルは時間の95%について正しいです。 1つの例外として、その言語を初めて使用する学生は、異なるプログラミング言語のスタイルを使用していました。自分のスタイルを他の言語のイディオムで掘り下げて読み直すと、私にとって警報ベルが取り除かれ、学生は完全な信用を得ました。しかし、そのような例外はまれです。
より高度なコードを検討するとき、これらは私の他の警告です:
スタイルの点では、私は一般的に見たくないです:
これらは悪いコードの手がかりのみです。プログラマーの意図がわからないため、悪いコードのように見えることがあっても、実際にはそうではない場合があります。たとえば、何かが過度に複雑に見えるという正当な理由がある可能性があります-別の考慮事項があった可能性があります。
個人的なお気に入り/おもしろい:IDE生成された名前が確定します。TextBox1がシステムの主要かつ重要な変数である場合、コードレビューが行われます。
完全に未使用の変数、特に変数が使用されている変数名と同様の名前を持っている場合。
多くの人が言及しました:
//TODO: [something not implemented]
私はそのようなものが実装されたことを望みますが、少なくとも彼らは書き留めました。私が悪いと思うのは:
//TODO: [something that is already implemented]
あなたがそれらを削除することを決して気にしないならば、Todoは価値がなくて混乱しています!
メソッド名の接続詞:
public void addEmployeeAndUpdatePayrate(...)
public int getSalaryOrHourlyPay(int Employee) ....
明確化:これが警報ベルを鳴らす理由は、メソッドが 単一責任の原則 に違反している可能性があることを示しているためです。
すべてを読むには、下にスクロールする必要がある方法。
明らかにGPL化されたソースコードを商用のクローズドソースプログラムにリンクしています。
それは差し迫った法的問題を引き起こすだけでなく、私の経験では、通常、コードの別の場所にも反映されている不注意または無関心を示しています。
言語にとらわれない:
TODO: not implemented
_int function(...) { return -1; }
(「実装されていません」と同じ)0
_、_-1
_またはnull
の誤用または矛盾した使用。言語固有(C++):
array new
_。printf
が含まれます。Microsoft C++固有:
C++/OOP固有:
列挙型やグローバルに定義された変数ではなく、多くのテキストブロックを使用する。
良くない:
if (itemType == "Student") { ... }
より良い:
private enum itemTypeEnum {
Student,
Teacher
}
if (itemType == itemTypeEnum.Student.ToString()) { ... }
ベスト:
private itemTypeEnum itemType;
...
if (itemType == itemTypeEnum.Student) { ... }
奇妙なインデントスタイル。
いくつかの非常に人気のあるスタイルがあり、人々はその議論を墓に持ち込みます。しかし、時々私は誰かが本当にまれな、あるいは自家製のインデントスタイルを使用しているのを見ます。これはおそらく、彼らが自分以外の誰かとコーディングしていないことを示しています。
弱い型指定のパラメーターまたはメソッドの戻り値。
public DataTable GetEmployees() {...}
public DateTime getHireDate(DataTable EmployeeInfo) {...}
public void FireEmployee(Object EmployeeObjectOrEmployeeID) {...}
コードのにおい:ベストプラクティスに従わない
誰もが平均以上のドライバーだと思っているその自明があるので、この種のことはいつも私を心配します。
ここにニュースフラッシュがあります。世界の人口の50%が平均的な知能を下回っています。わかりましたので、一部の人々は正確に平均的な知性を持っているでしょうが、うるさくしましょう。また、愚かさの副作用の1つは、自分の愚かさを認識できないことです。これらのステートメントを組み合わせると、見栄えがよくありません。
コードを見ると、すぐに警報音が鳴ります。
多くの良いことが言及されてきましたが、一般的にベストプラクティスに従わないとはコードのにおいのようです。
ベストプラクティスは、通常、無作為に発明されたものではなく、多くの場合、そこには理由があります。多くの場合、それは主観的ですが、私の経験では、それらはほとんど正当化されます。ベストプラクティスに従うことは問題ではないはずです。なぜそうなのか疑問に思っている場合は、それを無視したり、不満を言ったりするのではなく、調査してください。正当化されていることもあれば、そうでないこともあります。
ベストプラクティスの1つの例は、1行しか含まれていない場合でも、すべてのifブロックでカーリーを使用することです。
if (something) {
// whatever
}
あなたはそれが必要だとは思わないかもしれませんが、私は最近 read がバグの主要な原因であることを認識しています。常にブラケットを使用することは、 Stack Overflow でも説明されており、ifステートメントにブラケットがあることを確認することも rule で [〜#〜] pmd [〜#〜] 、Javaの静的コードアナライザー。
「ベストプラクティスであるため」は、「なぜこれを行っているのですか?」何かがベストプラクティスである理由を明確にできない場合、それはベストプラクティスではなく、迷信です。
if...else
_ブロックに似ているのではなく、_if...else if...[...]...else
_ブロックになります$lesseeloginaccountservice
_if
ステートメント。コードの例:if (!($lessee_obj instanceof Lessee && $lessee_obj != NULL))
に切り詰めたif ($lessee_obj == null)
一般的な例外をキャッチ:
try {
...
} catch {
}
または
try {
...
} catch (Exception ex) {
}
リージョンの過剰使用
通常、使用するリージョンが多すぎると、クラスが大きすぎることがわかります。これは、コードのそのビットについてさらに調査する必要があることを示す警告フラグです。
コードが絶対パスでファイルを正当に参照する必要がある例を誰かが思いつくことができますか?
「これは、frozサブシステムの設計が完全に失敗しているためです」と言うコメント。
それは段落全体に行きます。
彼らは、次のリファクタリングを行う必要があると説明しています。
しかし、それはしませんでした。
今、彼らは、時間や能力の問題のために、その時に上司からそれを変更することができないと言われたかもしれませんが、それはおそらく人々が取るに足りないためでした。
監督者がj.randomを考えている場合。プログラマはリファクタリングを行うことができないので、スーパーバイザはそれを行うべきです。
いずれにせよ、これは起こりますが、コードは分割されたチームによって作成されたものであり、可能なパワーポリティクスを備えていることは知っています。
実話。あなたに起こる可能性があります。
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
#define ...
もちろん、いかなる種類のドキュメントや、時々入れ子になった#define
s
明示的な削除ステートメントを含むC++コード(スマートポインター実装の内部を調べている場合を除く)。 'delete'はメモリ管理の 'goto'です [〜#〜] imho [〜#〜] 。
これと密接に関連している、次のようなコード:
// Caller must take ownership
Thing* Foo::Bar()
(そして、コメントがあれば幸運を祈ります!)スマートポインターがロケットサイエンスであるとは異なります。 std::auto_ptr
はこの種のこと(ドキュメント化とがコメントで表現された意図を強制する)のために作成され、 standard の一部です=年齢を問わず。
これらが合わさって、愛されていないレガシーコード、または90年代前半のどこかに行き詰まった考え方を持つメンテナーが悲鳴を上げます。
言語の基本機能を再実装する関数。たとえば、文字列の ".length"プロパティの呼び出しではなく、JavaScriptに "getStringLength()"メソッドが表示された場合、問題があることがわかります。
作成しようとしている抽象化の理解が不十分であることを示すクラス命名規則。または、抽象化をまったく定義していません。
極端な例がVBで見たクラスで、Data
というタイトルで、30,000 +行以上だった... firstこれは、少なくとも半ダースの他のファイルに分割された部分的なクラスでした。ほとんどのメソッドは、FindXByYWithZ()
のような名前のストアドプロシージャのラッパーでした。
それほど劇的な例ではありませんが、完全に一般的なタイトルを与えられ、後でそれを後悔したので、私たちは皆、不十分に考えられたクラスにロジックを「ダンプ」したと確信しています。
ON ERROR RESUME NEXT
Classicを維持する必要があるASP=アプリケーションは悲しいことにほとんどのASP.NET開発者にとって必要ですが、一般的なインクルードファイルを開いて、最初の行でそれが魂を破壊することを確認します。
コードが何をするか、または何をすべきかについてのコメントやドキュメントがない場合(つまり、「コードはドキュメント」です)。
接尾辞が数値のメソッドまたは変数(例:Login2())。
try { //do something } catch{}
論理的に実行パスに入ることができないコード。
var product = repository.FindProduct(id);
log.Info("Found product " + product.Name);
if (product != null)
{
// This will always happen
}
else
{
// **This won't ever happen**
}
または
if (messages.Count > 0)
{
// Do stuff with messages
}
else if (messages.Count == 1)
{
// **I hope this code isn't important...**
}
私のJava中心の視点から:
final
がありません。catch
は、有用なコードなしでブロックします(悪い:コメント、printf、ロギング、または単に空)。適切にスコープ指定され、型指定された変数を定義するのではなく、ユーザーインターフェイスの非表示オブジェクト(テキストボックスなど)を使用して値を格納します。
いつでも以下を読みます:
//Don't put in negative numbers or it will crash the program.
または似たようなもの。その場合は、アサートしてください!実行時にこれらの値を必要としないことをデバッガーに知らせ、コードが呼び出し元との契約を明確にしていることを確認してください。
このタイプのコード:
if (pflayerdef.DefinitionExpression == "NM_FIELD = ' '" || One.Two.nmEA == "" ||
One.Two.nmEA == " " || One.Two.nmEA == null ||
One.Two.nmEA == " ")
{
MessageBox.Show("BAD CODE");
return;
}
これは実際のライブプロダクションコードベースからです!
マジックナンバーについては、別の場所で使用されている場合は問題があり、変更するには、いくつかの場所で同期する必要があります。ただし、1か所に1つの数値がある場合でも、1つの数値が1つの場所でまだ使用されていることを示す定数が1つある場合と同じです。
さらに、定数はアプリケーションであまり場所がない場合があります。多くのデータベースアプリでは、これらの項目はアプリごとまたはユーザー設定ごとにデータベースに保存する必要があります。そして、それらの実装を完了するには、この設定とUI内の場所とエンドユーザードキュメントのメモが含まれます...これらはすべて、数が5のときに誰もが完全に満足している場合、オーバーエンジニアリングとリソースの浪費です。そして5です。)
数字や文字列は、その場所の外でこの数字を使用する必要が生じるまでそのままにしておくことができると思います。次に、物事をより柔軟な設計にリファクタリングします。
文字列については...私は引数を知っていますが、これは1つの文字列から1つの定数への変換を行う意味がないもう1つの場所です。特に、配置されている文字列が常に実装されている場合(たとえば、外部アプリケーションから生成され、 'Overdue'のように短くて認識可能なステータス文字列を持つインポートなど)がある場合。とにかく1か所だけで使用されている場合に、「期限切れ」をSTATUS_OVERDUEに変換することには多くの意味があります。
柔軟性、再利用、またはエラーチェックに必要なメリットが実際にもたらされない場合は、複雑さを増やさないようにしてください。柔軟性が必要な場合は、適切にコーディングしてください(リファクタリングなど)。しかし、それが必要なければ...
密結合コード。特に、コードの途中で多くのものがハードコードされている場合(ネットワークプリンターの名前、IPアドレスなど)は特にそうです。これらは構成ファイルまたは定数にある必要がありますが、以下は最終的に問題を引き起こすだけです:
if (Host_ip == '192.168.1.5'){
printer = '192.168.1.123';
} else
printer = 'prntrBob';
いつの日か、ボブは終了し、彼のプリンターの名前が変更されます。いつかプリンタが新しいIPアドレスを取得します。いつか192.168.1.5がボブのプリンターで印刷したいと思うでしょう。
私の好きなマントラ:あなたが住んでいる場所がそれを維持する必要があることを知っている殺人精神病者のようなコードを常に書いてください!
プログラマが何年も前にJava 5:
最新のマルチスレッド化された方法を知らない。
SQLの場合:
最初の大きな指標は、暗黙の結合の使用です。
次は、次のようなWHERE句と組み合わせたtableBでの左結合の使用です。
WHERE TableB.myField = 'test'
あなたがそれが間違った結果を与えることを知らないなら、あなたが書くどんなクエリも正しい結果を与えるとは私は信用できません。
当社のレガシーVB6コードでは、モジュールまたはフォームのコードページを開いて、パブリックまたはグローバルでいっぱいの画面を見つけることができます#&@! @&!!(*!#プログラム全体から参照されている上部で宣言された変数。ARGH!!!!
(うーん、私はそれを取り出さなければなりませんでした:-))
このようなもの
x.y.z.a[b].c
これはバイオハザードのようなにおいがします。これだけのメンバー参照は決して良い兆候ではありません。そして、これは私が使用しているコードの典型的な表現です。
このようなもので何か
// TODO: anything could be here!
編集:私がこれを本番コードで意味していることを修飾する必要があります。しかし、ソース管理にコミットしたコードでさえ、これはまだ良くありません。しかし、それは私が私のすべての緩い終わりを縛ったので私が一日の終わりを終わらせたいという点で個人的なことかもしれません:)
編集2:確立されたコードでこれを目にしたときに、私が意味したことをさらに修飾する必要があります。何年も前のもので、バグを修正しています。 TODOが表示され、アラームベルが鳴り始めます。 TODO(私にとって)は、コードが何らかの理由で終了しなかったことを意味します。
Javaでのsynchronized
キーワードの使用。
synchronized
を正しく使用した場合、問題はありません。しかし、私が使用しているコードでは、誰かがスレッドセーフなコードを書き込もうとするたびに、間違っているようです。したがって、このキーワードが表示された場合、コードの残りの部分に特に注意する必要があることを知っています...
より良い構造で最適化できるコードののぞき穴の最適化。プレーンC/C++/Java/C#でのバイナリ検索が適切(かつ高速)である場合に、インラインアセンブリで実装される線形検索。
人はいくつかの基本的な概念を欠いている、または優先順位の感覚を持っていません。後者ははるかに心配です。
@FinnNk、コメントアウトされたコードについてあなたに同意します。私を本当に困らせるのはこのようなものです:
for (var i = 0; i < num; i++) {
//alert(i);
}
または、テストやデバッグのためにそこにあり、コメントアウトされてコミットされたもの。たまにしか発生しない場合は大した問題ではありませんが、どこにでもあるとコードが乱雑になり、何が起こっているのかがわかりにくくなります。
重要な原則に違反するもの。たとえば、いくつかのアンチパターンが提案されています(マジックナンバー- http://en.wikipedia.org/wiki/Anti-pattern を参照)。アンチパターンは、問題を引き起こすためにも呼ばれています(脆弱性、メンテナンスの悪夢など)。また、SOLID原則の違反に注意してください- http://en.wikipedia.org/wiki/Solid_(object-oriented_design) も参照してください。階層の分離(UI内のデータアクセスなど)は考慮しません。コーディング標準とコードレビューがあると、これに対処できます。
これらのほとんどはJava経験からのものです:
コードが混乱しているように見える場合: "todo"と "note to self"を含むコメントと冗談。ある時点でデバッグ目的でのみ使用されたが、削除されなかったコード。ライターが誰もそれを後で読むとは考えなかったことを示唆する名前を持つ変数または関数。多くの場合、これらの名前は非常に長く扱いにくいものになります。
また、コードにリズムがない場合。大幅に異なる長さの関数。同じ命名規則に準拠していない関数。
やや関連性がある:プログラマが手書きの字がずさんだといつも緊張します。あるいは、彼らが悪い作家や悪いコミュニケーターである場合。
私はかつて、請負業者がintから文字列、あいまいな名前へのポインタを含む文字列までのすべての標準データ型をtyepdefしたプロジェクトに取り組みました。彼のアプローチはコードを理解することを本当に困難にしました。私に警告する別のスタイルは時期尚早な柔軟性です。私がかつて取り組んだ製品には、DLLの完全なコードがあり、予測できない順序で読み込まれていました。これらすべては、将来の進化に対応するためです。いくつかのDLLは、移植性のためにスレッドラッパーを使用していました。プログラムをデバッグしたり、ソースコードを読んでフローを予測したりするのは悪夢でした。定義はヘッダーファイル全体に散らばっていました。コードが2番目のバージョンを超えて存続しなかったのは当然のことです。
考えられるすべての入力を与えられたメソッドの一部が見つかることもありますが、それでも実行されることはないので、そもそも混乱している人がいないはずです。例は...
メソッドが管理者スーパーユーザーのコンテキスト内でのみ呼び出すことができ、リクエストユーザーが管理者スーパーユーザーでないかどうかを確認している場合...:/
抑制の欠如を示す不満のコメント:
//I CAN'T GET THIS F^#*&^ING PIECE OF S$^* TO COMPILE ON M$VC
彼らは不機嫌であるか、プログラミングで後退が避けられないことを学んだほど十分に経験していないかのどちらかです。
そんな人と一緒に働きたくない。
これは他の人が挙げたものに比べて少しマイナーな症状ですが、私はPythonプログラマーであり、コードベースでこれをよく目にします:
try:
do_something()
except:
pass
これは通常、プログラマーがなぜdo_something
は失敗する可能性があります(またはそれが何をするか)-コードがクラッシュしなくなるまで、彼はただ「いじくり回し」ました。
よりJava風の背景から来ている人のために、そのブロックはPythonと同等です
try {
doSomething();
} catch (Exception e) {
// Don't do anything. Don't even log the error.
}
Pythonは、キーボードの割り込みやforループからの脱出など、「例外的でない」コードの例外を使用します。
あちこちにゲッターがあると、私はびっくりします。
そして特別なこと:他のゲッターに委任するゲッター。
これは悪いことです。それは、それを書いた人がオブジェクト指向の基本を理解していないことを意味します。つまり、カプセル化です。つまり、データがどこにあるのか、そのデータに作用するメソッドがあるはずです。
委任は、すべてのゲッターではなく、1つのメソッドに対するものです。原則は「教えてください、お願いしないでください」です。 1つのことをオブジェクトに伝え、1000のことを要求しないで、自分で実行してください。
ゲッターは私をビックリさせます。それは、他のoop原則がハードコアに違反することになることを意味します。
タイプ情報がありません。
これらのメソッドシグネチャを見てください。
public List<Invoice> GetInvoices(Customer c, Date d1, Date d2)
public GetInvoices(c, d1, d2)
(1)には明快さがあります。関数を呼び出すために必要なパラメーターを正確に把握しており、関数が何を返すかは明らかです。
(2)では不確実性のみがあります。使用するパラメーターがわからず、何かが発生した場合に関数が何を返すかわかりません。あなたは事実上、プログラミングに対して非効率的な試行錯誤のアプローチを使用せざるを得ません。