apeescape2.com
  • メイン
  • Uiデザイン
  • 革新
  • デザイナーライフ
  • 収益と成長
技術

クリーンなコードと例外処理の技術

例外はプログラミング自体と同じくらい古いものです。プログラミングがハードウェアで、または低レベルのプログラミング言語を介して行われていた時代には、プログラムのフローを変更し、ハードウェアの障害を回避するために例外が使用されていました。今日、ウィキペディア 例外を定義します なので:

特別な処理を必要とする異常または例外的な条件–プログラム実行の通常のフローを変更することがよくあります…

そして、それらを処理するには、次のものが必要です。



特殊なプログラミング言語構造またはコンピューターハードウェアメカニズム。

そのため、例外には特別な処理が必要であり、未処理の例外は予期しない動作を引き起こす可能性があります。結果はしばしば壮観です。 1996年、有名な アリアン5ロケット打ち上げ失敗 未処理のオーバーフロー例外が原因でした。 歴史上最悪のソフトウェアバグ 未処理または誤って処理された例外に起因する可能性のある他のいくつかのバグが含まれています。

時間が経つにつれて、これらのエラー、および無数の他のエラー(おそらく、それほど劇的ではありませんでしたが、関係者にとっては壊滅的でした)は、次のような印象を与えました。 例外は悪い 。

しかし、例外は現代のプログラミングの基本的な要素です。それらは私たちのソフトウェアをより良くするために存在します。例外を恐れるのではなく、例外を受け入れ、その恩恵を受ける方法を学ぶ必要があります。この記事では、例外をエレガントに管理し、それらを使用して、より保守しやすいクリーンなコードを作成する方法について説明します。

例外処理:それは良いことです

の台頭とともに オブジェクト指向プログラミング (OOP)、例外サポートは現代のプログラミング言語の重要な要素になっています。今日では、堅牢な例外処理システムがほとんどの言語に組み込まれています。たとえば、Rubyは次の典型的なパターンを提供します。

begin do_something_that_might_not_work! rescue SpecificError => e do_some_specific_error_clean_up retry if some_condition_met? ensure this_will_always_be_executed end

前のコードに問題はありません。ただし、これらのパターンを使いすぎるとコードの臭いが発生し、必ずしも有益であるとは限りません。同様に、それらを誤用すると、実際にはコードベースに多くの害を及ぼし、コードベースを脆弱にしたり、エラーの原因を難読化したりする可能性があります。

例外を取り巻く汚名は、プログラマーに途方に暮れることをしばしば感じさせます。例外を回避できないのは現実ですが、例外は迅速かつ断固として対処する必要があるとよく言われます。これから説明するように、これは必ずしも真実ではありません。むしろ、例外を適切に処理して、コードの他の部分と調和させる方法を学ぶ必要があります。

以下は、例外を受け入れ、それらを利用してコードを保持するのに役立ついくつかの推奨プラクティスです。 保守可能 、 拡張可能 、および 読み取り可能 :

  • 保守性 :現在の機能を壊したり、さらにバグを導入したり、時間の経過とともに複雑さが増したためにコードを完全に放棄したりすることを恐れずに、新しいバグを簡単に見つけて修正できます。
  • 拡張性 :既存の機能を壊すことなく、新しい要件または変更された要件を実装して、コードベースに簡単に追加できます。拡張性は柔軟性を提供し、コードベースの高レベルの再利用を可能にします。
  • 読みやすさ :掘り下げるのに時間をかけずに、コードを簡単に読んでその目的を見つけることができます。これは、バグやテストされていないコードを効率的に発見するために重要です。

これらの要素は、私たちが呼ぶかもしれないものの主な要因です 清潔さ または 品質 、これは直接的な測定ではありませんが、このコミックで示されているように、前のポイントの複合効果です。

そうは言っても、これらのプラクティスに飛び込んで、それぞれがこれら3つの測定値にどのように影響するかを見てみましょう。

注意: Rubyの例を示しますが、ここで示すすべての構成には、最も一般的なOOP言語で同等のものがあります。

常に独自のApplicationErrorを作成してください階層

ほとんどの言語には、他のOOPクラスと同様に、継承階層で編成されたさまざまな例外クラスが付属しています。コードの可読性、保守性、拡張性を維持するには、基本例外クラスを拡張するアプリケーション固有の例外の独自のサブツリーを作成することをお勧めします。この階層を論理的に構築するために時間を投資することは、非常に有益です。例えば:

class ApplicationError

アプリケーション例外階層の例:StandardErrorが最上位にあります。 ApplicationErrorはそれを継承します。 ValidationErrorとResponseErrorはどちらもそれを継承しています。 RequiredFieldErrorとUniqueFieldErrorはValidationErrorから継承しますが、BadRequestErrorとUnauthorizedErrorはResponseErrorから継承します。

製品の価格弾力性を知ることで、エコノミストは次のことが可能になります。

アプリケーション用の拡張可能で包括的な例外パッケージがあると、これらのアプリケーション固有の状況の処理がはるかに簡単になります。たとえば、より自然な方法で処理する例外を決定できます。これにより、コードの可読性が向上するだけでなく、アプリケーションとライブラリ(gems)の保守性も向上します。

読みやすさの観点から、読みやすくなっています。

rescue ValidationError => e

読むより:

rescue RequiredFieldError, UniqueFieldError, ... => e

たとえば、保守性の観点から、JSON APIを実装し、独自のClientErrorを定義しました。クライアントが不正な要求を送信したときに使用される、いくつかのサブタイプがあります。これらのいずれかが発生した場合、アプリケーションは応答でエラーのJSON表現をレンダリングする必要があります。考えられる各クライアントエラーをループしてそれぞれに同じハンドラーコードを実装するよりも、ClientError sを処理する単一のブロックに修正またはロジックを追加する方が簡単です。拡張性の観点から、後で別のタイプのクライアントエラーを実装する必要がある場合、ここですでに適切に処理されていると確信できます。

さらに、これは、コールスタックの早い段階で特定のクライアントエラーに対して追加の特別な処理を実装したり、途中で同じ例外オブジェクトを変更したりすることを妨げるものではありません。

# app/controller/pseudo_controller.rb def authenticate_user! fail AuthenticationError if token_invalid? || token_expired? User.find_by(authentication_token: token) rescue AuthenticationError => e report_suspicious_activity if token_invalid? raise e end def show authenticate_user! show_private_stuff!(params[:id]) rescue ClientError => e render_error(e) end

ご覧のとおり、この特定の例外を発生させても、さまざまなレベルでの処理、変更、再発生、親クラスハンドラーによる解決が妨げられることはありませんでした。

ここで注意すべき2つのこと:

  • すべての言語が例外ハンドラー内からの例外の発生をサポートしているわけではありません。
  • ほとんどの言語では、 新着 ハンドラー内からの例外により、元の例外が永久に失われるため、エラーの元の原因を見失うことを避けるために、同じ例外オブジェクトを(上記の例のように)再発生させることをお勧めします。 (あなたがこれをしているのでなければ 意図的に )。

決してrescue Exception

つまり、基本例外タイプのキャッチオールハンドラーを実装しようとしないでください。すべての例外を大規模に救助またはキャッチすることは 決して グローバルにベースアプリケーションレベルであろうと、1回だけ使用される小さな埋め込みメソッドであろうと、どの言語でも良いアイデアです。救助したくないException実際に起こったことを曖昧にし、保守性と拡張性の両方を損なうからです。構文エラーのように単純な場合、実際の問題が何であるかをデバッグするのに膨大な時間を浪費する可能性があります。

# main.rb def bad_example i_might_raise_exception! rescue Exception nah_i_will_always_be_here_for_you end # elsewhere.rb def i_might_raise_exception! retrun do_a_lot_of_work! end

前の例でエラーに気付いたかもしれません。 returnタイプミスです。最新のエディターは、この特定のタイプの構文エラーに対してある程度の保護を提供しますが、この例は、rescue Exceptionの方法を示しています。私たちのコードに害を及ぼします。例外の実際のタイプ(この場合はNoMethodError)が対処されたり、開発者に公開されたりすることはありません。これにより、サークルでの実行に多くの時間を浪費する可能性があります。

決してrescue必要以上の例外

前のポイントは、このルールの特定のケースです。例外ハンドラーを過度に一般化しないように常に注意する必要があります。理由は同じです。必要以上に多くの例外をレスキューするときはいつでも、アプリケーションロジックの一部をアプリケーションの上位レベルから隠すことになり、開発者が自分で例外を処理する能力を抑制することは言うまでもありません。これは、コードの拡張性と保守性に深刻な影響を及ぼします。

同じハンドラーで異なる例外サブタイプを処理しようとすると、責任が多すぎるファットコードブロックが導入されます。たとえば、リモートAPIを使用するライブラリを構築している場合、MethodNotAllowedErrorを処理します。 (HTTP 405)は、通常、UnauthorizedErrorの処理とは異なります。 (HTTP 401)、両方ともResponseError sで​​すが。

これから説明するように、特定の例外を処理するのに適したアプリケーションの別の部分が存在することがよくあります。 ドライ 仕方。

だから、定義する 単一責任 あなたのクラスまたはメソッドの、そして この責任要件を満たす最低限の例外を処理する 。たとえば、メソッドが 株式情報の取得 リモートAPIから、その情報のみを取得することから発生する例外を処理し、他のエラーの処理をこれらの責任のために特別に設計された別のメソッドに任せる必要があります。

def get_info begin response = HTTP.get(STOCKS_URL + '#{@symbol}/info') fail AuthenticationError if response.code == 401 fail StockNotFoundError, @symbol if response.code == 404 return JSON.parse response.body rescue JSON::ParserError retry end end

ここでは、株式に関する情報のみを取得するために、このメソッドの契約を定義しました。処理します エンドポイント固有のエラー 、不完全または不正な形式のJSON応答など。認証が失敗または期限切れになった場合、または在庫が存在しない場合は処理されません。これらは他の誰かの責任であり、DRYの方法でこれらのエラーを処理するためのより良い場所があるはずのコールスタックに明示的に渡されます。

例外をすぐに処理したいという衝動に抵抗する

これは最後のポイントを補完するものです。例外は、呼び出しスタックの任意のポイント、およびクラス階層の任意のポイントで処理できるため、どこで処理するかを正確に知ることは不可解な場合があります。この難問を解決するために、多くの開発者は例外が発生したらすぐに処理することを選択しますが、これを熟考することに時間を費やすと、通常、特定の例外を処理するためのより適切な場所を見つけることになります。

Railsアプリケーションで見られる一般的なパターンの1つ( 特にJSONのみのAPIを公開するもの )は、次のコントローラーメソッドです。

# app/controllers/client_controller.rb def create @client = Client.new(params[:client]) if @client.save render json: @client else render json: @client.errors end end

(これは技術的には例外ハンドラーではありませんが、機能的には同じ目的を果たします。@client.saveは例外が発生した場合にのみfalseを返すためです。)

ただし、この場合、すべてのコントローラーアクションで同じエラーハンドラーを繰り返すことはDRYの反対であり、保守性と拡張性が損なわれます。代わりに、例外伝播の特殊な性質を利用して、それらを1回だけ処理できます。 親コントローラークラス内 、ApplicationController:

# app/controllers/client_controller.rb def create @client = Client.create!(params[:client]) render json: @client end # app/controller/application_controller.rb rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity def render_unprocessable_entity(e) render json: { errors: e.record.errors }, status: 422 end

このようにして、すべてのActiveRecord::RecordInvalidを確認できます。エラーは適切に処理され、DRYで処理されます-ベースの1か所でApplicationControllerレベル。これにより、特定のケースを下位レベルで処理したい場合、または単にそれらを適切に伝播させたい場合に、それらをいじる自由が得られます。

すべての例外を処理する必要があるわけではありません

gemまたはライブラリを開発する場合、多くの開発者は機能をカプセル化しようとし、例外がライブラリから伝播することを許可しません。ただし、特定のアプリケーションが実装されるまで、例外を処理する方法が明確でない場合があります。

取りましょう ActiveRecord 理想的な解決策の例として。ライブラリは、完全を期すための2つのアプローチを開発者に提供します。ザ・ save メソッドは、例外を伝播せずに処理し、falseを返すだけです。 save! 失敗すると例外が発生します。これにより、開発者は特定のエラーケースを別の方法で処理するか、単に一般的な方法で障害を処理するかを選択できます。

しかし、そのような完全な実装を提供するための時間やリソースがない場合はどうでしょうか。その場合、不確実性がある場合は、 例外を公開する 、そしてそれを野生に放します。

その理由は次のとおりです。私たちはほぼ常に要件の移動に取り組んでおり、例外が常に特定の方法で処理されるという決定を下すと、実際に実装に悪影響を及ぼし、拡張性と保守性を損ない、巨大なものを追加する可能性があります 技術的負債 、特にライブラリを開発するとき。

株価を取得する株式APIコンシューマーの前の例を見てください。不完全で不正な形式の応答をその場で処理することを選択し、有効な応答が得られるまで同じ要求を再試行することを選択しました。ただし、後で要件が変更される可能性があるため、リクエストを再試行するのではなく、保存された過去の在庫データにフォールバックする必要があります。

この時点で、依存プロジェクトはこの例外を処理しないため、ライブラリ自体を変更して、この例外の処理方法を更新する必要があります。 (どうして彼らはできたのでしょうか?以前は彼らにさらされたことはありませんでした。)また、私たちの図書館に依存しているプロジェクトの所有者に通知する必要があります。このようなプロジェクトが多数ある場合、このエラーが特定の方法で処理されることを前提として構築されている可能性が高いため、これは悪夢になる可能性があります。

これで、依存関係の管理がどこに向かっているのかがわかります。見通しは良くありません。この状況は非常に頻繁に発生し、多くの場合、ライブラリの有用性、拡張性、および柔軟性を低下させます。

だからここに結論があります: 例外の処理方法が不明な場合は、適切に伝播させてください 。例外を内部的に処理するための明確な場所が存在する場合は多くありますが、例外を公開する方がよい場合も多くあります。したがって、例外の処理を選択する前に、考え直してください。 経験則としては、 主張する エンドユーザーと直接やり取りする場合の例外の処理について。

規則に従う

Ruby、さらにはRailsの実装は、method_namesを区別するなど、いくつかの命名規則に従います。およびmethod_names! 「強打」で。 Rubyでは、バングはメソッドがそれを呼び出したオブジェクトを変更することを示し、Railsでは、期待される動作の実行に失敗した場合にメソッドが例外を発生させることを意味します。特にライブラリをオープンソース化する場合は、同じ規則を尊重するようにしてください。

新しいmethod!を書くとしたらRailsアプリケーションに大きな影響を与える場合は、これらの規則を考慮に入れる必要があります。このメソッドが失敗したときに例外を発生させることを強制するものは何もありませんが、規則から逸脱することにより、このメソッドはプログラマーに、実際には例外を処理しないのに、自分で例外を処理する機会が与えられると誤解させる可能性があります。

ジム・ワイリッチに起因する別のRubyの慣習は、 failを使用するメソッドの失敗を示します 、およびraiseのみを使用する例外を再発生させる場合。

cssメディアクエリブレークポイントはに基づいている必要があります

余談ですが、失敗を示すために例外を使用するため、ほとんどの場合failを使用します。 raiseではなくキーワードRubyのキーワード。 Failとraiseは同義語であるため、failはメソッドが失敗したことをより明確に伝えることを除いて、違いはありません。私がraiseを使用するのは、例外をキャッチして再発生するときだけです。これは、ここでは失敗ではなく、明示的かつ意図的に例外を発生させるためです。これは私が従う文体の問題ですが、他の多くの人がそうしているとは思えません。

他の多くの言語コミュニティでは、例外の処理方法に関してこのような規則を採用しています。これらの規則を無視すると、コードの可読性と保守性が損なわれます。

Logger.log(すべて)

もちろん、この方法は例外だけに適用されるわけではありませんが、必要なことが1つある場合は 常に ログに記録されますが、それは例外です。

ロギングは非常に重要です(Rubyが出荷するのに十分重要です ログ 標準バージョンで)。これはアプリケーションの日記であり、アプリケーションがどのように成功したかを記録するよりもさらに重要なのは、アプリケーションがいつどのように失敗したかをログに記録することです。

不足はありません ロギングライブラリ または ログベースのサービス とデザインパターン。何が起こったのかを確認し、何かが正しくないかどうかを調査できるように、例外を追跡することが重要です。適切なログメッセージにより、開発者は問題の原因を直接指摘でき、計り知れない時間を節約できます。

そのクリーンなコードの信頼性

クリーンな例外処理により、コード品質が月に送られます。 つぶやき

例外は、すべてのプログラミング言語の基本的な部分です。それらは特別で非常に強力であり、それらと戦うことに疲れ果てるのではなく、それらの力を活用してコードの品質を向上させる必要があります。

この記事では、例外ツリーを構造化するためのいくつかの優れたプラクティスと、それらを論理的に構造化することが読みやすさと品質にどのように役立つかについて詳しく説明しました。 1つの場所または複数のレベルで、例外を処理するためのさまざまなアプローチを検討しました。

「すべてを捕まえる」のは悪いことであり、それらを浮かせて泡立たせても問題ないことがわかりました。

例外をDRY方式で処理する場所を調べたところ、例外が最初に発生したときまたは発生した場所で処理する義務がないことがわかりました。

正確にいつ議論したか です 悪い考えである場合はそれらを処理することをお勧めします。疑わしい場合は、それらを伝播させることをお勧めします。

最後に、規則に従う、すべてをログに記録するなど、例外の有用性を最大化するのに役立つ他のポイントについて説明しました。

これらの基本的なガイドラインを使用すると、コード内のエラーケースをより快適に、自信を持って処理し、例外を本当に例外的なものにすることができます。

感謝します Avdi grimm と彼の素晴らしい話 並外れたルビー 、この記事の作成に大いに役立ちました。

関連: Ruby開発者向けのヒントとベストプラクティス

Fastlane:クルーズコントロールでのiOSオートメーション

モバイル

Fastlane:クルーズコントロールでのiOSオートメーション
Swiftプログラミングの学習:プライムタイムの準備はできていますか?

Swiftプログラミングの学習:プライムタイムの準備はできていますか?

モバイル

人気の投稿
マークダウンを学ぶ:ソフトウェア開発者のためのライティングツール
マークダウンを学ぶ:ソフトウェア開発者のためのライティングツール
美徳シグナリングの芸術:なぜこれほど多くのブランドがそれを間違えるのか
美徳シグナリングの芸術:なぜこれほど多くのブランドがそれを間違えるのか
価格弾力性2.0:理論から現実世界へ
価格弾力性2.0:理論から現実世界へ
ApeeScapeとFacebook-グローバル仮想オフィスの作成
ApeeScapeとFacebook-グローバル仮想オフィスの作成
サイバーセキュリティ:すべてのCEOとCFOが知っておくべきこと
サイバーセキュリティ:すべてのCEOとCFOが知っておくべきこと
 
WebRTCアプリケーションを構築する1年:スタートアップエンジニアリングの教訓
WebRTCアプリケーションを構築する1年:スタートアップエンジニアリングの教訓
PHP 7の概要:新機能と廃止点
PHP 7の概要:新機能と廃止点
ラベルなしデータを使用した半教師あり画像分類
ラベルなしデータを使用した半教師あり画像分類
GWTがブラウザの拡張現実を解き放つ方法
GWTがブラウザの拡張現実を解き放つ方法
UXスケッチについて知っておくべきことすべて
UXスケッチについて知っておくべきことすべて
人気の投稿
  • c / c ++チュートリアル
  • フロントエンドの開発者およびデザイナー
  • レスポンシブウェブデザインはどのように機能しますか
  • あなたはグラフィックアーティストのためのシステムをまとめています
  • マイクロソフト紺碧の機械学習チュートリアル
  • 実生活でのゲシュタルト原則の例
カテゴリー
プロジェクト管理 デザイナーライフ 製品ライフサイクル ライフスタイル ツールとチュートリアル 仕事の未来 バックエンド 収益性と効率性 モバイルデザイン 革新

© 2021 | 全著作権所有

apeescape2.com