何千もの記事がhabrahabrのみの純粋なコードに捧げられています。 自分で書きたいと思ったことはなかったでしょう。 しかし、会社がソフトウェア開発、特にAndroid向けの開発にキャリアを結び付けたい人向けのコースを実施した後、私の意見は変わりました。
この記事は、古典「Robert K. Martin:Clean Code」のヒントに基づいています。 私はそれらから、学生の間で問題の形で最も頻繁に遭遇したものを選択しました。 これらのヒントは、商用Androidアプリケーションの開発における私の経験に基づいて書かれています。 したがって、以下のヒントはすべてのAndroidプロジェクトで機能するわけではなく、他のシステムについては話していない。
ヒントには、ほとんど必要ないコード例が提供されています。 奇妙なことに、ほとんどの学生には同じ間違いがありました。 すべてのコード例が考案されており、実際のコードとの一致はランダムです。
一般的なヒント
1.コードは読みやすく、理解可能で、明白でなければなりません。
プログラマは、新しいコードを書くのではなく、書いたコードを読んで分析することにほとんどの時間を費やします。 コードは読みやすく、理解しやすく、予測可能な動作であることが重要です。 これにより、同僚と各コードの機能を理解するために最小限の時間を費やすことができます。 予測可能な動作を備えた明確なコードは、コードの作成者以外が変更を行う際のエラーの可能性を減らします。
2.コードを書くときは、プロジェクトチームが採用したJavaコード規約またはその他の仕様を遵守する必要があります。
仕様は、本のテキストのルールと比較できます。 各段落がテキストのフォント、サイズ、色で区別される本を読みたくない人はほとんどいないと思います。 したがって、コードを使用すると、同じスタイルでコードを読むのがはるかに簡単になります。
お名前
1.クラス、関数、変数、およびパラメーターの名前は、それらに定義されている意味を伝える必要があります。
かなり明白なアドバイスですが、常にそれに従うとは限りません。 ここでは、例として、本の目次を引用できます。すべての章が単に「章」と呼ばれる場合、その本質が理解できない。 クラスと関数の名前は、実装の詳細に飛び込むことなく、それらの目的を報告する必要があります。
クラスMyActivity, CustomView, MyAdapter
は1つだけ言っています。これはAndroid SDKの一部ではないということです。 SecondActivity
は、これはAndroid SDKの一部ではなく、プロジェクトには別のアクティビティがありますが、事実はありません=)
クラスに正しい名前を付けます: MainActivity, FoodsActivity, FoodCartActivity, PhoneInputView
。 MainActivity
明らかに、アプリケーションのメイン画面。 FoodsActivity
製品のリストを含む画面。 PhoneInputView
電話番号を入力するためのコンポーネント。
次の変数名は役に立たず、命名規則に違反しています。
private TextView m_textview_1; private TextView m_textview_2; private TextView m_textview_3;
コンストラクターパラメーターと同様:
public Policy(int imgId,int imgId2)
2.パブリッククラスとファイルの名前は一致する必要があります。
次のクラスがあります
public class ProductsActivity extends AppCompatActivity { ... }
ファイルの名前は「products_Activity.java」です。
これは正しくありません。 パブリッククラスの名前とJavaファイルは一致する必要があります。
3.名前には、ラテンアルファベットの文字のみを使用し、数字、アンダースコア、ハイフンは使用しないでください。 例外は、標準(GOST、ISO)からの名前、定数名の単語を区切るための下線です。
上記の例: m_textview_1
。 多くの場合、 lastName
ではなくuserName2
書き込まれuserName2
、これは正しくありません。
4.小文字の「L」と「O」をローカル変数の名前として使用する必要はありません。 「1」や「0」と区別することは困難です。
不自然な例ですが、私の練習で似たようなものに出会いました
private void s(int a[]) { for (int l = 0; l < a.length; l++) { for (int O = a.length - 1; O > l; O--) { if (a[O - 1] > a[O]) { int o = a[0 - 1]; a[O - 1] = a[O]; a[O] = o; } } } }
このバブルソート関数には1つのエラーがあります。数秒で見つけることができます=)?
このようなコードは読みにくく、書くときは検索に非常に長い時間がかかる可能性があるミスを犯しやすいです。
5.名前の接尾辞にタイプを指定する必要はありません。
accountList
代わりに、 accountList
を記述するだけです。 これにより、変数自体の名前を変更せずに、いつでも変数のタイプを変更できます。
さらに悪いのは、 nameString, ageFloat
です。
例外は、アクティビティ、フラグメント、ビュー、ウリなどのAndroid SDKクラスの相続人です。 NewsSynsServiceという名前では 、クラスが「サービス」であり、ニュースの同期を担当していることがすぐにわかります。 nameView, photoView
のview
接尾辞を使用して、 nameView, photoView
を使用すると、レイアウト変数を他のレイアウト変数と簡単に区別できます。 ビュー名は通常、名詞で始まります。 ただし、ボタン名は、動詞:buyButtonで始めるのが最適です。
6.名前には、数学の用語、アルゴリズムの名前、設計パターンなどを含めることができます。
コードの作者ではなく、名前BitmapFactory
見ると、このクラスの意味がすぐに理解されます。
7.命名時にプレフィックスは不要です。
m_user, mUser
代わりにm_user, mUser
単にuser
と記述されuser
。 最新のIDEの静的フィールドにsプレフィックスを指定する必要はありません。
Android SDKのソースコードは、最初のバージョンの作成とコードベースの今日への継承の制限のため、ここではインジケーターではありません。
public static final String s_default_name = "name";
s_は、静的フィールドの名前の先頭では役に立ちません 。 さらに、定数の名前は大文字で書く必要があります。
public static final String DEFAULT_NAME = "name";
8.クラス名には名詞を使用する必要があります。
クラスは実世界のオブジェクトのようなものです。 したがって、名前にAccountsFragment, User, Car, CarModel
名詞を使用する必要があります。
クラスManager, Processor, Data, Info
を呼び出す必要はありません。 それらは一般的すぎます。 2〜4ワードのクラス名は、単なるData
よりも優れています。
9.クラス名は大文字で始める必要があります。
単語はアンダースコアで区切らないでください。 GoodsFragment, BaseFragment
表記に従う必要があります: GoodsFragment, BaseFragment
10.概念ごとに1つの単語を使用します。
1つのクラスfetch, retrieve, get
を使用すると混乱します。 クラスがCustomer
と呼ばれた場合、この型の関数のクラス変数とパラメーターの名前はuser
ではなくcustomer
と呼ばれるべきです。
機能
1.関数は1つの「操作」のみを実行する必要があります。 彼女はそれをうまくやらなければなりません。 そして彼女は他に何もするべきではありません。
「操作」とは、1つの数学的な操作や別の関数の呼び出しではなく、同じ抽象レベルのアクションを意味します。 関数が複数の操作を実行することを確認するには、その関数から別の関数を抽出する必要があります。 関数を抽出してもコードの単純な再配置しか得られない場合、関数をさらに壊しても意味がありません
たとえば、 ProductsAdapter
クラスのsetProducts
関数があります。
public class ProductsAdapter extends RecyclerView.Adapter<ProductHolder> { public void setProducts(List<Product> newProducts) { products.clear(); for (Product newProduct : newProducts) { if (!TextUtils.isEmpty(newProduct.getName())) { products.add(newProduct); } } notifyDataSetChanged(); }
関数内には、1) newProducts
フィルタリング、2) products
への新しい値のクリーニングと挿入、3)アダプターnotifyDataSetChanged
更新の3つの主要な操作があります。
newProducts要素のフィルタリングは、別のメソッドで、さらにはクラスの別のメソッド(プレゼンターなど)で行う必要があります。 すでにフィルター処理されたデータはProductsAdapter
送られます。
次のようにコードをやり直すことをお勧めします。
public void setProducts(List<Product> newProducts) { products.clear(); products.addAll(newProducts); notifyDataSetChanged(); }
newProducts
パラメーターには、既にフィルター処理された製品のリストが含まれています。
また、 java products.clear(); products.addAll(newProducts);
java products.clear(); products.addAll(newProducts);
別の機能を追加しますが、これにはあまり意味がありません。 notifyDataSetChanged
をDiffUtilに置き換えることをおnotifyDataSetChanged
します 。これにより、リスト内のセルをより効率的に更新できます。
2.関数のサイズは8〜10行にする必要があります。
3画面の機能は機能ではなく、*****です。 また、関数のサイズを8〜10行のコードに制限することに常に成功するとは限りませんが、このために努力する必要があります。 明らかな理由で例を挙げません。
大規模な関数は、読み取り、変更、およびテストが困難です。 大きな関数を小さな関数に分割すると、小さな詳細が隠されるため、メイン関数の意味を理解しやすくなります。 関数を強調表示することにより、メイン関数のコードの重複を確認して取り除くことができます。
3.関数の本体では、すべてが同じ抽象レベルにある必要があります。
private void showArticelErrorIfNeed(Article article) { if (validateArticle(article)) { String errorMessage = "Article " + article.getName() + " is incorrect"; showArticleError(errorMessage); } else { hideArticleError(); } }
ローカル変数errorMessage
の値の計算は、関数内の残りのコードよりも抽象化のレベルが低くなります。 したがって、 java "Article " + article.getName() + " is incorrect"
コードjava "Article " + article.getName() + " is incorrect"
、別の関数に入れる方java "Article " + article.getName() + " is incorrect"
適切です。
4.関数の名前と渡されるパラメーターは、関数が何をしているかを報告する必要があります。
コードブロックを別の関数に割り当てる理由は、コードが何をするのかを説明したいという願望かもしれません。 これを行うには、適切な関数名と関数パラメーターを指定する必要があります。
public Product find(String a) { … }
検索が行われるフィールドは明確ではなく、関数の入力に送信されます。
次の形式でリメイクすることをお勧めします。
@Nullable public Product findProductById(@NonNull String id) { … }
関数の名前は、 id
フィールドによるProduct
検索があることを示しています。 この関数は、入力でヌル以外の値を受け入れます。 Product
見つからない場合、「null」が返されます。
Robert C. Martinは、関数名の一部としてパラメーターを使用することを推奨しています。
public void add(Product product) { … }
関数呼び出しは次のようになります。
add(product);
プロジェクトでは、そのような方法を見たことはありません。 このメソッドを使用せずにフルネームを書くことをお勧めします:
public void addProduct(Product product){ … }
5.関数名は動詞で始まる必要があります。 長い名前は、意味のない短い名前よりも優れています。
public static void start(Context context) {...}
名前start
は、関数が何を開始するかを示していません。 身体を覗くだけで、関数がアクティビティを開くことが明らかになります。
そして、関数の目的はすでに名前で明確になっているはずです。
public static Intent makeIntent(Context context) {...} // .
6.フラグ関数(ブール値)を引数に渡す代わりに、関数を2つの関数に分割することをお勧めします
多くの場合、このフラグにより、実行ロジックを分岐するときに、フラグの値に応じて関数のサイズが大きくなります。 このような場合、この関数を2つに分割することを検討する必要があります。 渡されたフラグに応じた関数の異なる動作は、必ずしも明らかではありません。
7.重複したコードは別の機能で取り出す必要があります。
lightButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { titleView.setTextAppearance(R.style.lightText); descriptionView.setTextAppearance(R.style.lightText); titleView.setBackgroundResource(R.color.lightBack); descriptionView.setBackgroundResource(R.color.lightBack); } }); (... ) greyButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { titleView.setTextAppearance(R.style.greyText); descriptionView.setTextAppearance(R.style.greyText); titleView.setBackgroundResource(R.color.greyBack); descriptionView.setBackgroundResource(R.color.greyBack); } });
setOnClickListener内のコードは、スタイルのみが異なります。 このコードは別のメソッドに配置する必要があります。
private void changeStyle(@StyleRes int textSyleResource, @ColorRes int backgroundColorResource) { titleView.setTextAppearance(textSyleResource); descriptionView.setTextAppearance(textSyleResource); titleView.setBackgroundResource(colorResource); descriptionView.setBackgroundResource(RcolorResource); }
8.空のオブジェクトを返すことができる場合は、「null」関数を渡さないでください。
public List<Product> findProductsByType(String type) { if (TextUtils.isEmpty(type)) { return null; } List<Product> result = new ArrayList<>(); for (Product product : products) { if (product.getType().equals(type)) { result.add(product); } } return result; } : @NonNull public List<Product> findProductsByType(@NonNull String type) { if (TextUtils.isEmpty(type)) { return Collections.emptyList(); } List<Product> result = new ArrayList<>(); for (Product product : products) { if (product.getType().equals(type)) { result.add(product); } } return result; }
これにより、NullPointerexceptionエラーを回避でき、呼び出しの場所でnullのチェックを記述する必要がなくなります。
Kotlinは、nullを渡したり返すという悪い習慣を解くのに役立ちます。)
書式設定
1.クラス内のコードは、新聞記事のように抽象化レベルの降順で上下に読む必要があります。 最初はパブリック関数、次にプライベート関数があります。
アドバイスの主なアイデアは、ファイルを開くと、プログラマーが上からファイルを表示し始めるということです。 最初にすべてのパブリック関数を配置すると、クラスオブジェクトを使用した基本操作、クラスの責任、およびそれを使用できる場所を理解しやすくなります。 このヒントは、プロジェクトがインターフェイス上に構築される場合に適しています。
2.ファイルを常に上下に移動する必要がないように、関連する概念/機能を近くに配置する必要があります。 ある関数が別の関数を呼び出す場合、呼び出される関数は呼び出し可能な関数の下に配置され(可能な場合)、空の文字列で区切られる必要があります。
このアドバイスは、前のアドバイスと矛盾する場合があります。 チームに優先的なアドバイスを選択する場合は、プロジェクト全体を通してそれに従う必要があります。
3.コードの連続する各グループは、1つの完全な思考を表す必要があります。 思考は空の行を使用してコード内で互いに分離されているため、空の行を使いすぎないでください
リンクされた行のグループは、記事の段落のようなものです。 赤い線または空の線を使用して、段落を区切ります。 したがって、コードでは、空の行を使用して、意味の異なる行のグループを分離する必要があります。
4.クラスインスタンス変数、静的定数はすべて、クラスの先頭に1か所に配置する必要があります。
クラスの先頭で、パブリック定数が宣言され、次にプライベート定数が宣言され、その後プライベート変数が続きます。
正しい発表:
public class ImagePickerActivity extends AppCompatActivity { public final static String KEY_CODE_HEX_COLOR = "hex_color"; public final static String KEY_CODE_PICKED_IMAGE = "picked_image"; private final static int REQUEST_CODE_GET_IMAGE = 2; private final int REQUEST_CODE_HEX_COLOR = 1; private Uri imageUri; @Override protected void onCreate(Bundle savedInstanceState) {
5.スペースを使用して、コードを読みやすくします。
- 演算記号をオペランドから分離する数学演算で:
int x = a + b;
- 関数パラメーターの分離:
private Article createNewArticle(String title, String description)
- 渡される関数パラメーター:
createNewArticle(title, description)
6.「マジックナンバー、ストリング」は定数でレンダリングする必要があります!!!
おそらく最も人気のあるアドバイスですが、それでも違反を続けています。
StringBuilder builder= new StringBuilder(); for (int i = days; i > 0; i /= 10) { builder.insert(0, i % 10); }
問題のステートメントを読んだ後、コードで「10」という数字が使用された理由を理解しました。 この数値を定数に入れて、意味のある名前を付けるとよいでしょう。
文字列は、特に複数の場所で使用される場合、定数に入れる価値があります。
クラス
1.クラスには、1つの「責任」、1つの変更理由が必要です。
たとえば、RecyclerView.Adapterクラスの子孫は、ビューの作成とデータへのリンクを担当する必要があります。 要素のリストのソート/フィルターコードを含めることはできません。
多くの場合、RecyclerView.AdapterクラスはActivityと共にファイルに追加されますが、これは正しくありません。
2.空のメソッドがないか、親クラスからの呼び出しメソッドだけが必要です
@Override protected void onStart() { super.onStart(); }
3.親メソッドを呼び出さずに一部のメソッドを再定義する場合、これが実行できることを確認します。
親クラスのソースコード、ドキュメントをご覧ください。 Activity、Fragment、Viewライフサイクルの再定義されたメソッドは、必ず親クラスのメソッドを呼び出す必要があります。
オーバーライド時に親メソッドを呼び出す必要があることを示すアノテーション@CallSuper
警告があります。
その他のヒント
1. Android Studioのツールを使用して、コードの品質を改善します。
コードに下線を引いたり強調表示したりすると、何かが間違っている可能性があります。 Android Studioには、再配置、再フォーマット、メソッドの抽出、インラインメソッド、コードアナライザー、 ホットキーなどのツールがあります。
2.各メソッドにコメントする必要はありません。コードは自己文書化する必要があります。
コメントでは、コードの動作ではなく、意図と理由を説明する必要があることに注意してください。 コメントを作成するには、コメントを最新の状態に保つ責任があります。
3.線、色、サイズはリソース内にある必要があります(xml)
書く必要はありません:
private static final String GREEN_BLUE = "#33c89b"; String.valueOf(" " + days + " ")
例外は、テストからのファイル、模擬フォルダーです。 このようなファイルは、リリースバージョンに含めるべきではありません。
4.コンテキストをRecyclerView.Adapterに渡す必要はありません。 コンテキストは任意のビューから取得できます。 RecyclerView.Adapterのクリックスルー要素は、クリック処理メソッドのビューを直接変更するのではなく、notifyItemChangedを使用して変更する必要があります。
holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { item.buyNow(); bindViewHolder(holder, position); } });
onClick
メソッドでは、 onClick
がbindViewHolder
呼び出されbindViewHolder
、これは正しくありません。
5. RecyclerView.ViewHolderでは、リスト内のオブジェクトの位置を決定する代わりに、getAdapterPositionを呼び出し、onBindViewHolderメソッドから位置を渡しません
正しく書くには:
private static class ItemHolder extends RecyclerView.ViewHolder { public ItemHolder(View itemView) { super(itemView); itemView.setOnClickListener(view -> { int position = getAdapterPosition(); ... }); } }
または、位置の代わりにソースデータオブジェクトへのリンクを渡すことができます
itemView.setOnClickListener((v) -> { productClickListener.onProductClick(product); });
6.複数形には数量文字列(複数形)を使用します。 正しい語尾を選択するロジックをコードに記述する代わりに。
7.大きなビットマップデータをバンドルに保存しないでください。 バンドルサイズは制限されています
8.「id」をxmlに登録すると、Android自体が回転したときに標準のビューの状態を復元します。