JavaRush /Java Blog /Random-JA /一般的なプログラミング スタイルのガイド
pandaFromMinsk
レベル 39
Минск

一般的なプログラミング スタイルのガイド

Random-JA グループに公開済み
この記事は、アカデミック コース「Advanced Java」の一部です。このコースは、Java の機能を効果的に使用する方法を学習するのに役立つように設計されています。この教材では、オブジェクトの作成、競合、シリアル化、リフレクションなどの「高度な」トピックが取り上げられています。このコースでは、Java テクニックを効果的に習得する方法を学びます。詳細はこちら
コンテンツ
1. はじめに 2. 変数のスコープ 3. クラスフィールドとローカル変数 4. メソッドの引数とローカル変数 5. ボックス化とアンボックス化 6. インターフェイス 7. 文字列 8. 命名規則 9. 標準ライブラリ 10. 不変性 11. テスト 12. 次へ。 .. 13. ソースコードをダウンロードする
1. はじめに
チュートリアルのこの部分では、Java における優れたプログラミング スタイルとレスポンシブ デザインの一般原則について引き続き説明します。これらの原則の一部については、ガイドの前の章ですでに説明しましたが、Java 開発者のスキルを向上させることを目的として、多くの実践的なヒントが提供されます。
2. 変数のスコープ
第 3 部 (「クラスとインターフェイスの設計方法」) では、スコープの制約を考慮して、クラスとインターフェイスのメンバーに可視性とアクセシビリティをどのように適用できるかについて説明しました。ただし、メソッドの実装で使用されるローカル変数についてはまだ説明していません。Java 言語では、すべてのローカル変数は、宣言されるとスコープを持ちます。この変数は、宣言された場所からメソッド (またはコードのブロック) の実行が完了する時点まで可視になります。一般に、従うべき唯一のルールは、ローカル変数を使用する場所のできるだけ近くで宣言することです。典型的な例を見てみましょう。 for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } どちらのコード部分でも、変数のスコープは、これらの変数が宣言されている実行ブロックに制限されています。ブロックが完了するとスコープが終了し、変数は非表示になります。これはより明確に思えますが、Java 8 のリリースとラムダの導入により、ローカル変数を使用するこの言語のよく知られたイディオムの多くが廃止されつつあります。ループの代わりにラムダを使用した前の例の例を示します。 ローカル変数が関数の引数になり、それが引数として forEach Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); メソッド に渡されることがわかります。
3. クラスフィールドとローカル変数
Java の各メソッドは、特定のクラス (Java8 の場合は、メソッドがデフォルト メソッドとして宣言されているインターフェイス) に属します。実装で使用されるクラスまたはメソッドのフィールドであるローカル変数間では、名前の競合が発生する可能性があります。Java コンパイラは、複数の開発者がその変数を使用するつもりであっても、使用可能な変数の中から正しい変数を選択する方法を認識しています。最新の Java IDE は、コンパイラの警告や変数の強調表示を通じて、そのような競合が発生しそうになったことを開発者にうまく伝えます。しかし、コードを書きながらそのようなことを考えたほうがよいでしょう。例を見ることをお勧めします。 public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } この例は非常に簡単に見えますが、これは罠です。 CalculateValueメソッドはローカル変数 を導入し、それを操作して同じ名前のクラス フィールドを非表示にします。この行は value += value; クラス フィールドの値とローカル変数の合計であるはずですが、代わりに別の処理が行われています。適切な実装は次のようになります (this キーワードを使用)。 public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } この例はある意味単純ですが、場合によってはデバッグと修正に数時間かかる可能性があるという重要な点を示しています。
4. メソッドの引数とローカル変数
経験の浅い Java 開発者が陥りやすいもう 1 つの落とし穴は、メソッドの引数をローカル変数として使用することです。Java では、非定数引数に値を再割り当てすることができます (ただし、これは元の値には影響しません): 上記 public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } のコード スニペットは洗練されていませんが、問題を明らかにするのに良い仕事をしています: 引数 strが割り当てられています異なる値 (基本的にはローカル変数として使用されます)。すべての場合 (例外なく)、この例を使用しなくても問題ありませんし、使用する必要があります (たとえば、引数を定数として宣言するなど)。例: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } この単純なルールに従うことで、ローカル変数を導入する場合でも、指定されたコードをトレースして問題の原因を見つけることが容易になります。
5. 梱包と開梱
ボックス化とアンボックス化は、Java でプリミティブ型 ( int、long、double など) を対応する型ラッパー ( Integer、Long、Doubleなど)に 変換するために使用される手法の名前です。ジェネリクスをいつ使用する方法とそのチュートリアルのパート 4 で、プリミティブ型をジェネリックスの型パラメータとしてラップすることについて説明したときに、これが実際に動作していることをすでに確認しました。Java コンパイラはオートボックス化を実行してそのような変換を隠蔽しようと最善を尽くしますが、場合によってはこれが期待よりも不十分で、予期しない結果が生じることがあります。例を見てみましょう。 public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); 上記のコード スニペットは正常にコンパイルされます。 ただし、 Longlong の間で変換を行っている行では NullPointerExceptionがスローされます。このような場合のアドバイスは、プリミティブ型を使用することをお勧めします (ただし、これが常に可能であるとは限らないことはすでにわかっています)。 // блок
6. インターフェース
チュートリアルのパート 3「クラスとインターフェイスの設計方法」では、インターフェイスとコントラクト プログラミングについて説明し、可能な限り具体的なクラスよりもインターフェイスを優先する必要があることを強調しました。このセクションの目的は、実際の例でこれを実証することで、最初にインターフェイスについて検討することを奨励することです。インターフェイスは特定の実装に関連付けられていません (デフォルトのメソッドを除く)。これらは単なる契約であり、一例として、契約の実行方法に多くの自由と柔軟性を提供します。この柔軟性は、実装に外部システムまたはサービスが関与する場合にさらに重要になります。単純なインターフェイスとその実装の例を見てみましょう。 public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } 上記のコード スニペットは、典型的なインターフェイス パターンとその実装を示しています。この実装では、外部 HTTP サービス ( http://api.geonames.org/ ) を使用して、特定の場所のタイムゾーンを取得します。ただし、コントラクトはインターフェイスに依存するため、データベースや通常のフラット ファイルなどを使用して、インターフェイスの別の実装を導入するのは非常に簡単です。これらを使用すると、インターフェイスはテスト可能なコードを設計するのに非常に役立ちます。たとえば、すべてのテストで外部サービスを呼び出すことは必ずしも現実的ではないため、代わりに最も単純な実装 (スタブなど) を実装することが合理的です。 この実装は、 TimezoneService public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } }インターフェイスが必要な 場所であればどこでも使用でき、外部コンポーネントへの依存関係からテスト スクリプトを削除します。このようなインターフェイスを効果的に使用する優れた例の多くが、Java 標準ライブラリ内にカプセル化されています。コレクション、リスト、セット - これらのインターフェイスには、シームレスに交換できる複数の実装があり、コントラクトを活用するときに交換できます。例えば: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. ストリングス
文字列は、Java と他のプログラミング言語の両方で最もよく使用される型の 1 つです。Java 言語は、すぐに使用できる連結操作と比較操作をサポートすることで、多くの日常的な文字列操作を簡素化します。さらに、標準ライブラリには、文字列操作を効率化する多くのクラスが含まれています。これはまさにこのセクションで説明する内容です。Java では、文字列は UTF-16 エンコーディングで表される不変オブジェクトです。文字列を連結する (または元の文字列を変更する操作を実行する) たびに、 Stringクラスの新しいインスタンスが作成されます。このため、連結操作は非常に非効率になり、 Stringクラスの中間インスタンスが多数作成される(一般にガベージが作成される) 可能性があります。しかし、Java 標準ライブラリには、文字列操作を便利にすることを目的とした 2 つの非常に便利なクラスが含まれています。これらは StringBuilderStringBufferです(これらの唯一の違いは、 StringBufferがスレッド セーフであるのに対し、 StringBuilder はその逆であることです)。これらのクラスの 1 つが使用されている例をいくつか見てみましょう。 StringBuilder/StringBuffer final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); の使用は文字列の操作に推奨される方法ですが、2 つまたは 3 つの文字列を連結する最も単純なシナリオでは過剰に見える可能性があります。そのため、通常の加算演算子 ( (「+」) 例: 連結を簡素化するための最良の代替策は、多くの場合、文字列フォーマットと Java 標準ライブラリを使用して、静的な String.formatヘルパー メソッドを提供することです。これは、数値、記号、日付/時刻などを含む豊富な形式指定子のセットをサポートします (完全な詳細については、リファレンス ドキュメントを参照してください)。 String.format メソッドは、 さまざま なデータ型から文字列を生成するためのクリーンで軽量なアプローチを提供します。 最新の Java IDE は、 String.formatメソッドに渡された引数から形式仕様を解析し、不一致が検出された場合に開発者に警告できること は注目に値します。 String userId = "user:" + new Random().nextInt( 100 ); String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12%
8. 命名規則
Java は、開発者に命名規則に厳密に従うことを強制しない言語ですが、コミュニティは、標準ライブラリと他の Java プロジェクトの両方で Java コードの一貫性を保つための単純なルールのセットを開発しました。
  • パッケージ名は小文字です: org.junit、com.fasterxml.jackson、javax.json
  • クラス、列挙、インターフェイス、注釈の名前は大文字で記述されます: StringBuilder、Runnable、@Override
  • フィールドまたはメソッドの名前 ( static Finalを除く) は、isEmpty、format、addAllの Camel 表記法で指定されます。
  • 静的な最終フィールドまたは列挙定数名は、LOG、MIN_RADIX、INSTANCE のように大文字でアンダースコア (「_」) で区切られます
  • ローカル変数またはメソッド引数は、キャメル表記法 ( str、newLength、minimumCapacity)で入力されます。
  • ジェネリックスのパラメーター型名は、大文字の 1 文字で表されます ( T、U、E)。
これらの単純な規則に従うことで、作成するコードは簡潔で、スタイル的には他のライブラリやフレームワークと区別がつかなくなり、同じ人によって開発されたように感じられます (規則が実際に機能するまれなケースの 1 つ)。
9. 標準ライブラリ
どのような種類の Java プロジェクトに取り組んでいるとしても、Java 標準ライブラリはあなたの強い味方です。確かに、荒削りな部分や奇妙な設計上の決定があることに同意するのは難しいですが、99% の場合、専門家によって書かれた高品質のコードです。調べてみる価値はあります。Java の各リリースでは、既存のライブラリに多くの新機能が追加され (古い機能にはいくつかの問題が発生する可能性があります)、また、多くの新しいライブラリも追加されます。 Java 5 では、 java.util.concurrentパッケージの一部として新しい同時 実行ライブラリが導入されました。 Java 6 は、スクリプト作成 ( javax.scriptパッケージ) およびJava コンパイラ API ( javax.toolsパッケージの一部として)のサポート (あまり知られていません) を提供しました。Java 7 では、 java.util.concurrentに多くの改良が加えられ、 java.nio.fileパッケージに新しい I/O ライブラリが導入され、 java.lang.invokeで動的言語がサポートされました。そして最後に、Java 8 では、待望の 日付/時刻が java.timeパッケージに追加されました。プラットフォームとしての Java は進化しており、上記の変化とともに Java が進化することは非常に重要です。プロジェクトにサードパーティのライブラリやフレームワークを含めることを検討するときは、必要な機能が標準 Java ライブラリに含まれていないことを確認してください (もちろん、Java ライブラリよりも先に、特殊で高性能なアルゴリズムの実装が多数あります)。アルゴリズムは標準ライブラリに含まれていますが、ほとんどの場合、実際には必要ありません)。
10. 不変性
ガイド全体およびこの部分における不変性は、注意を促すものとして残ります。真剣に受け止めてください。設計したクラスまたは実装したメソッドが不変性を保証できれば、ほとんどの場合、同時に変更されることを恐れることなく、どこでも使用できます。これにより、開発者としての生活 (そしてできればチーム メンバーの生活) が楽になります。
11. テスト
テスト駆動開発 (TDD) の実践は Java コミュニティで非常に人気があり、コード品質の基準を引き上げています。TDD が提供するさまざまな利点にもかかわらず、今日の Java 標準ライブラリにテスト フレームワークやサポート ツールが含まれていないのは残念です。 ただし、テストは最新の Java 開発に必要な部分となっており、このセクションではJUnitフレームワークを使用したいくつかの基本的なテクニックを見ていきます。JUnit では、基本的に、各テストはオブジェクトの予期される状態または動作に関する一連のステートメントです。優れたテストを作成する秘訣は、テストをシンプルかつ短く保ち、一度に 1 つのことをテストすることです。演習として、 String.format が目的の結果を返す文字列セクションの関数であることを確認する一連のテストを作成してみましょう。 package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } どちらのテストも非常に読みやすく、実行はインスタンスです。現在、平均的な Java プロジェクトには何百ものテスト ケースが含まれており、開発プロセス中にリグレッションや機能に関する迅速なフィードバックが開発者に提供されます。
12. 次へ
ガイドのこの部分では、Java でのプログラミングの実践とこのプログラミング言語のマニュアルに関連する一連の説明を完了します。次回は言語の機能に戻り、例外、その型、それらをいつどのように使用するかについて Java の世界を探索します。
13. ソースコードをダウンロードする
これは、上級 Java コースの一般的な開発原則に関するレッスンでした。レッスンのソース コードは ここからダウンロードできます。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION