これは関連するQです: finally句を使用して、スタイルが悪い/危険な状態が返された後に作業を行うのですか?
参照されているQでは、finallyコードは使用される構造とプリフェッチの必要性に関連しています。私の質問は少し異なりますが、より広い聴衆に密接に関係していると思います。私の特定の例はC#winformアプリですが、これはC++/Java最終的に使用にも適用されます。
ブロック内に埋め込まれた例外や例外処理/クリーンアップに関係のないコードがたくさんあるかなりの数のtry-catch-finallyブロックに気づいています。そして、例外と処理に密接に関連するコードを使用して、非常にタイトなtry-catch-finallyブロックを作成するという私の偏見を認めます。ここに私が見ているもののいくつかの例があります。
Tryブロックには、スローする可能性のあるコードに至るまでに設定されている多くの予備呼び出しと変数があります。ロギング情報はセットアップを取得し、tryブロックでも実行されます。
最後に、ブロックにはフォーム/モジュール/コントロールのフォーマット呼び出し(アプリが終了しようとしているにもかかわらず、catchブロックで表現されている)があり、パネルなどの新しいオブジェクトが作成されます。
おおよそ:
methodName(...) { try { //メソッドのコードが多い... //スローする可能性のあるコード... //メソッドとリターンのコードがさらに多く... } catch(something) {/ /例外を処理します} 最終的に { //例外によるクリーンアップ、クローズ処理 //作成されたもののためのより多くのコード例外がスローされた可能性があります)... //おそらくさらにオブジェクトを作成します } }
コードは機能するので、それにはいくつかの価値があります。うまくカプセル化されておらず、ロジックは少し複雑です。私はコードをシフトしたりリファクタリングしたりする際のリスクに(痛々しく)精通しているので、私の質問は結局、同様に構造化されたコードで他の人の経験を知りたいということです。
悪いスタイルは変更を加えることを正当化しますか?誰かが同じような状況でひどくやけどを負ったことがありますか?その悪い体験の詳細を共有していただけませんか?私が反応しすぎて、スタイルがそれほど悪くないので、そのままにしておきますか?片付けをすることのメンテナンスの利点を得る?
開発者が作成した恐ろしいレガシーWindowsフォームコードを処理する必要があったときに、彼らが何をしているのか明確に知らなかった非常によく似た状況にありました。
まず第一に、あなたは過活動ではありません。これは悪いコードです。あなたが言ったように、catchブロックは中止して停止する準備をすることです。オブジェクト(特にパネル)を作成するときではありません。なぜこれが悪いのか説明することすらできません。
言われていること...
私の最初のアドバイスは次のとおりです:壊れていない場合は触れないでください!
あなたの仕事がコードを維持することであるならば、あなたはそれを壊さないように最善を尽くさなければなりません。私はそれが痛いことを知っていますが(私はそこに行ったことがあります)、すでに機能しているものを壊さないように最善を尽くす必要があります。
私の2番目のアドバイスは次のとおりです:さらに機能を追加する必要がある場合は、既存のコード構造をできるだけ維持して、コードを壊さないようにしてください。
例:適切な継承に置き換えられる可能性があると感じた恐ろしいswitch-caseステートメントがある場合は、注意を払い、慎重に検討してから、物事の移動を開始することを決定する必要があります。
あなたは間違いなくリファクタリングが正しいアプローチである状況を見つけるでしょうが、注意してください:リファクタリングコードはバグを導入する可能性が高いです。この決定は、開発者の観点からではなく、アプリケーション所有者の観点から行う必要があります。したがって、問題を修正するために必要な労力(お金)がリファクタリングに値するかどうかを考える必要があります。開発者が「コードが醜い」と思ったからといって、実際には壊れていないものを修正するのに数日費やすことを何度も目にしました。
私の3番目のアドバイスは次のとおりです。コードを壊すと火傷します。それがあなたの責任かどうかは関係ありません。
メンテナンスを依頼された場合、他の誰かが誤った判断を下したためにアプリケーションがバラバラになっていても、問題にはなりません。ユーザーの観点からは、以前は機能していましたが、現在は機能していません。 あなたがそれを壊しました!
Joelは、レガシーコードを書き直すべきではないいくつかの理由を説明している彼の記事に非常によく述べています。
http://www.joelonsoftware.com/articles/fog0000000069.html
そのため、そのようなコードについては本当に気分が悪くなります(そして、そのようなコードを書くべきではありません)が、それを維持することはまったく別の怪物です。
私の経験について:私は約1年間コードを維持する必要があり、結局私は最初からそれを書き直すことができましたが、一度にすべてではありませんでした。
何が起こったのかというと、コードが非常に悪かったため、新機能を実装することが不可能でした。既存のアプリケーションには、深刻なパフォーマンスとユーザビリティの問題がありました。結局、私は3〜4か月かかる変更を行うように求められました(主にそのコードの操作に通常よりも長い時間がかかったためです)。その部分全体(必要な新機能の実装を含む)を約5〜6か月で書き直すことができると思いました。私はこの提案を利害関係者に持ち込み、彼らはそれを書き直すことに同意しました(幸運にも私にとって)。
この作品を書き直した後、彼らは私が彼らがすでに持っていたものよりもはるかに良いものを提供できることを理解しました。したがって、アプリケーション全体を書き直すことができました。
私のアプローチは、それを少しずつ書き直すことでした。最初にUI全体(Windowsフォーム)を置き換え、次に通信層(Webサービス呼び出し)を置き換え始め、最後にサーバー実装全体(シッククライアント/サーバーの一種のアプリケーション)を置き換えました。
数年後、このアプリケーションは会社全体で使用される美しい重要なツールになりました。すべてを書き直さなければ、それは不可能だったと100%確信しています。
私はそれを行うことができましたが、重要な部分は、利害関係者がそれを承認し、お金の価値があることを彼らに納得させることができたことです。したがって、既存のアプリケーションを維持する必要がある一方で、何も壊さないように最善を尽くし、書き直しの利点について所有者を納得させることができる場合は、切り裂きジャックのようにそれを実行してみてください。
コードを変更する必要がない場合は、そのままにしておきます。ただし、バグを修正したり機能を変更したりする必要があるためにコードを変更する必要がある場合は、変更する必要があります。私見それは多くの場合、最初にそれをより小さく理解しやすい部分にリファクタリングしてから機能を追加すると、より安全でエラーが発生しにくくなります。手元に自動リファクタリングツールがある場合、これは比較的安全に実行でき、新しいバグが発生するリスクはごくわずかです。そして、私はあなたがマイケル・フェザーズの本のコピーを手に入れるべきであることを勧めます
http://www.Amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
これは、コードをよりテストしやすくする方法、ユニットテストを追加する方法、および新しい機能を追加するときにコードが壊れないようにするための貴重なヒントを提供します。
あなたがそれを正しく行うならば、うまくいけば、あなたのメソッドはtry-catch-finallyが間違った場所に無関係なコードを含まないように十分に単純になるでしょう。
呼び出すメソッドが6つあるとします。そのうち4つは、最初のメソッドがエラーなしで完了した場合にのみ呼び出す必要があります。 2つの選択肢を検討してください。
try {
method1(); // throws
method2();
method3();
method4();
method5();
} catch(e) {
// handle error
}
method6();
対
try {
method1(); // throws
}
catch(e) {
// handle error
}
if(! error ) {
method2();
method3();
method4();
method5();
}
method6();
2番目のコードでは、戻りコードのような例外を扱います。概念的には、これは次と同じです。
rc = method1();
if( rc != error ) {
method2();
method3();
method4();
method5();
}
method6();
例外をスローする可能性のあるすべてのメソッドをtry/catchブロックでラップする場合、言語機能に例外があることの意味は何ですか?メリットはなく、タイピングの数も増えます。
そもそも言語に例外がある理由は、古くからのリターンコードでは、多くの場合、エラーを返す可能性のある複数のメソッドがあり、各エラーがすべてのメソッドを完全に中止するという状況にあったためです。私のキャリアの初めに、昔のCの時代に、次のようなコードをいたるところに見ました。
rc = method1();
if( rc != error ) {
rc = method2();
if( rc != error ) {
rc = method3();
if( rc != error ) {
rc = method4();
if(rc != error ) {
method5();
}
}
}
}
method6();
これは非常に一般的なパターンであり、上記の最初の例に戻ることができるようにすることで、例外が非常にきちんと解決されました。各メソッドに独自の例外ブロックがあることを示すスタイルルールは、これをウィンドウから完全にスローします。それなら、なぜ気になるのか?
概念的にはユニットであるコードのセットよりもtryブロックが優先されるように常に努力する必要があります。コードユニットにエラーがある場合、コードユニットで他のコードを実行しても意味がないコードを囲む必要があります。
例外の本来の目的に戻ります。コードは実際に発生したエラーをすぐに簡単に処理できないことが多いため、例外が作成されたことを思い出してください。例外のポイントは、コードのかなり後の方でエラーを処理できるようにすること、またはスタックをさらに上にできるようにすることです。人々はそれを忘れ、多くの場合、問題のメソッドがこの例外をスローできることを単に文書化したほうがよい場合、ローカルでそれらをキャッチします。世界には次のようなコードが多すぎます。
void method0() : throws MyNewException
{
try {
method1(); // throws MyOtherException
}
catch(e) {
if(e == MyOtherException)
throw MyNewException();
}
method2();
}
ここでは、レイヤーを追加するだけで、レイヤーが混乱を招きます。これを行うだけです:
void method0() : throws MyOtherException
{
method1();
method2();
}
それに加えて、私のルールは、そのルールに従い、メソッドに2つ以上のtry/catchブロックがあることに気付いた場合、メソッド自体が複雑すぎて、複数のメソッドに分解する必要があるかどうかを質問する必要があるということです。
要点:tryブロックには、ブロック内のどこかでエラーが発生した場合に中止する必要があるコードのセットを含める必要があります。このコードセットが1行であるか1000行であるかは関係ありません。 (ただし、メソッドに1000行ある場合、他の問題があります。)