📜 ⬆️ ⬇️

Clean Code Tips for Java / Android Newbies

Thousands of articles are devoted to the topic of clean code on habrahabr alone. I never thought I would want to write my own. But after the company conducted courses for those who want to associate a career with software development, in particular, develop under Android, my opinion has changed.


The article is based on tips from the classics “Robert K. Martin: Clean Code”. I selected those that were most often encountered in the form of problems among students. These tips are written based on my experience in developing commercial Android applications. Therefore, the following tips are not suitable for all Android projects, I'm not talking about other systems.


Councils basically cited with code examples as it is NOT NECESSARY to do. Oddly enough, most students had the same mistakes. All code samples were invented, any coincidences with real-life code are random.


General tips


1. The code should be easy to read, understandable and obvious.


Programmers spend most of their time reading and analyzing written code, rather than writing new ones. It is important that your code is easily readable, understandable and with predictable behavior. This will allow colleagues and you after a lapse of time to spend the minimum time on understanding what each piece of code does. Understandable code with predictable behavior will reduce the likelihood of error when making changes not by the author of the code.


2. When writing code, you need to adhere to the Java Code Conventions or other specifications adopted by the project


The specification can be compared with the rules for the design of the text of the book. I think few people will want to read a book, where each paragraph is different in font, size and text color. So with the code, it is much easier to read the code decorated in the same style.


Name


1. The names of classes, functions, variables and parameters should convey the meaning laid down in them.


Quite obvious advice, but not always stick to it. Here, as an example, you can bring the table of contents of the book: if all the chapters are called simply "Chapters", then you can not understand the essence. Names of classes, functions should inform about their purpose without immersion in the details of implementation.


Class MyActivity, CustomView, MyAdapter says only one thing, that this is not part of the Android SDK and that's all. SecondActivity says that this is not part of the Android SDK and that there is another Activity in the project, but not a fact =)


Correctly call the classes: MainActivity, FoodsActivity, FoodCartActivity, PhoneInputView . MainActivity - it is obvious that the main diluting screen of the application. FoodsActivity - a screen with a list of products. PhoneInputView is a component for entering a phone number.

The following variable names are useless and violate the naming rules:


 private TextView m_textview_1; private TextView m_textview_2; private TextView m_textview_3; 

as well as constructor parameters:


 public Policy(int imgId,int imgId2) 

2. The name of the public class and the file must match.


There is the next class


 public class ProductsActivity extends AppCompatActivity { ... } 

The name of the file is “products_Activity.java”.
It is not right. The name of the public class and java-file must match.


3. In the names you need to use only letters of the Latin alphabet, no numbers, underscores and hyphens. Exceptions are the names of the standards (GOST, ISO), underscores for word separation in the names of constants.


Examples above: m_textview_1 . Often, instead of lastName write userName2 , which is not correct.


4. No need to use the lowercase “L” and “O” as the names of local variables, since they are difficult to distinguish from “1” and “0”.


A contrived example, but something similar I met in my practice


 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; } } } } 

There is one mistake in this bubble sort function, can you find it in seconds =)?
Such code is difficult to read and it is easy to make a mistake when writing, which can be searched for for a very long time.


5. No need to specify the type in the suffix name.


Instead of accountList you just need to write accounts . This will allow you to change the type of the variable at any time without renaming the variable itself.
And even worse is the nameString, ageFloat .


The exceptions are the heirs of the Android SDK classes: Activity, Fragment, View, Uri, etc. By the name NewsSynsService it is immediately clear that the class is a “service” and is responsible for synchronizing the news. Using the view suffix in nameView, photoView makes it easy to distinguish layout related variables from the rest. View names usually begin with a noun. But the names of the buttons are better to start with the verb: buyButton


6. Names can and should contain terms from mathematics, names of algorithms, design patterns, etc.


Seeing the name BitmapFactory , not the author of the code will immediately understand the meaning of this class.


7. No need to specify any prefixes when naming.


Instead of m_user, mUser simply written as user . It is unnecessary to specify the prefix s for static fields in modern IDEs.


The Android SDK sources are not an indicator here due to the age of creation of the first versions and the inheritance of the code base to our days.


 public static final String s_default_name = "name"; 

s_ to nothing at the beginning of the name of a static field. In addition, the name of the constants should be written in capital letters:


 public static final String DEFAULT_NAME = "name"; 

8. In the class name you need to use nouns.


Classes are like objects in the real world. Therefore, you need to use nouns for their names: AccountsFragment, User, Car, CarModel .


No need to call the classes Manager, Processor, Data, Info , because they are too general. A class name with a length of two to four words is better than just Data .


9. Class names must begin with a capital letter.


Words must NOT be separated by an underscore. Follow the notation of CamelCase: GoodsFragment, BaseFragment


10. Use one word for each concept.


Using fetch, retrieve, get in one class is confusing. If the class was called Customer , then the names of the class variables and parameters of functions of this type are better called customer , and not user .



Functions


1. A function must perform only one “operation”. She has to do it well. And she should not do anything else.


By “operation” one should understand not one mathematical operation or a call to another function, but actions at the same level of abstraction. To determine that a function performs more than one operation, you should try to extract another function from it. If extracting a function gives nothing but a simple rearrangement of the code, then it does not make sense to split the function further.


For example, there is a setProducts function of the ProductsAdapter class:


 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(); } 

Inside the function, there are three main operations: 1) filtering newProducts , 2) clearing and inserting new values ​​into products , 3) updating the adapter notifyDataSetChanged .


Filtering elements of newProducts should occur in another method and even in another in a class (for example, in a presenter). Already filtered data should come to ProductsAdapter .


It is better to alter the code as follows:


 public void setProducts(List<Product> newProducts) { products.clear(); products.addAll(newProducts); notifyDataSetChanged(); } 

The newProducts parameter contains the already filtered list of products.
You can still line java products.clear(); products.addAll(newProducts); java products.clear(); products.addAll(newProducts);
make a separate function, but I don’t see much point in this. It is better to replace notifyDataSetChanged with DiffUtil , this will allow updating the cells in the list more efficiently.


2. The size of the function should be 8-10 lines.


The function of 3 screens is not a function, it is *****. I, too, do not always manage to limit the size of the function to 8-10 lines of code, but I must strive for this. For obvious reasons, I will not give an example.


Large functions are difficult to read, modify, and test. Having broken a large function into small ones, it will be easier to understand the meaning of the main function, since small details will be hidden. Selecting functions you can see and get rid of code duplication in the main function.


3. In the body of the function, everything must be at the same level of abstraction.


 private void showArticelErrorIfNeed(Article article) { if (validateArticle(article)) { String errorMessage = "Article " + article.getName() + " is incorrect"; showArticleError(errorMessage); } else { hideArticleError(); } } 

Calculating the value of the local variable errorMessage has a lower level of abstraction than the rest of the code inside the function. Therefore, the java "Article " + article.getName() + " is incorrect" code java "Article " + article.getName() + " is incorrect" better to put in a separate function.


4. The name of the function and the parameters to be transferred should indicate what the function is doing.


The reason for separating a block of code into a separate function may be the desire in the explanation of what the code does. To do this, you must give the appropriate name of the function and the parameters of the function.


 public Product find(String a) { … } 

It is not clear by what field the search will occur, which is passed to the input of the function.


It is better to convert to the following form:


 @Nullable public Product findProductById(@NonNull String id) { … } 

The name of the function says that the Product is being searched by the id field. The function does not accept a “null” value as input. If Product not found, then “null” will return.


Robert C. Martin recommends using parameters as part of the function name:


 public void add(Product product) { … } 

The function call may look like this:


 add(product); 

On projects, I have not seen such a method. It is better not to use this method and write the full name:


 public void addProduct(Product product){ … } 

5. Function names must begin with a verb. Better long name than not meaningful short.


 public static void start(Context context) {...} 

The name start does not indicate what exactly the function starts. Just looking into the body, it becomes clear that the function opens the Activity.


And it should be clear the purpose of the function is already in name.


 public static Intent makeIntent(Context context) {...} //  . 

6. Instead of passing a flag (boolean) to the function arguments, it is better to split the function into two functions.


Often this flag causes an increase in the size of the function when branching execution logic depending on the value of the flag. In such cases, you should think about splitting this function into two. Different behavior of the function depending on the flag passed is not always obvious.


7. Duplicate code should be carried out in a separate function.


 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); } }); 

The code inside setOnClickListener differs only in style. This code should be put in a separate method:


 private void changeStyle(@StyleRes int textSyleResource, @ColorRes int backgroundColorResource) { titleView.setTextAppearance(textSyleResource); descriptionView.setTextAppearance(textSyleResource); titleView.setBackgroundResource(colorResource); descriptionView.setBackgroundResource(RcolorResource); } 

8. Do not pass or return “null” from the function when you can return an empty object.


  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; } 

This will avoid NullPointerexception errors and will not need to write null checks at call sites.


Kotlin will help you to unlearn the habit of transmitting and returning null.)


')

Formatting


1. The code in the class should be read from top to bottom as a newspaper article in descending order of the level of abstraction. First come the public functions, then the private ones.


The main idea of ​​the board is that when opening a file, the programmer starts to look at it from above. If you first place all public functions, it will be easier to understand the basic operations with objects of the class, the responsibility of the class and where it can be used. This tip comes up when a project is built on interfaces.


2. Related concepts / functions should be placed side by side so that there is no need to constantly move up and down the file. If one function calls another, then the called function must be located under the calling function (if possible) and separated by an empty string.


This advice may contradict the previous one. Having chosen the priority advice for the team, you should stick to it on the whole project.


3. Each successive group of lines of code should represent one complete thought. Thoughts are separated from each other in the code with the help of empty lines, but you should not overuse the empty lines too much.


A linked group of lines is like paragraphs in an article. To separate paragraphs, use a red line or an empty line. Similarly, in the code one should separate groups of strings that are different in meaning, using empty lines.


4. Variables of the class instance, static constants should be all at the beginning of the class in one place.


At the beginning of the class, public constants are declared, then private constants, followed by private variables.


Correct announcement:


 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. Use spaces to improve code readability.



6. "Magic numbers, strings" must be placed in constants !!!
Perhaps the most popular advice, but still continue to violate it.


 StringBuilder builder= new StringBuilder(); for (int i = days; i > 0; i /= 10) { builder.insert(0, i % 10); } 

Only after reading the statement of the problem, I understood why the number “10” was used in the code. It is better to take this number into a constant and give a meaningful name.


Strings should also be made into constants, especially if used in more than one place.


Classes


1. The class must have one “responsibility”, one reason for the change.


For example, heirs of the RecyclerView.Adapter class should be responsible for creating and linking the View with the given one. It should not contain the sort / filter code for the list of items.
Often, the RecyclerView.Adapter class is added to the file with Activity, which is not correct.


2. Do not need empty methods or just calling the method from the parent class


 @Override protected void onStart() { super.onStart(); } 

3. If you override any method without calling the parent method, then check that you can do this.


Take a look at the source of the parent classes documentation. Overriding methods of the life cycle of the Activity, Fragment, View must necessarily call the methods of the parent class.


There is an annotation @CallSuper warning of the need to call the parent method when overriding.



Tips about different things


1. Use Android Studio tools to improve the quality of your code.


If she emphasizes or highlights the code, then something may be wrong. Android Studio has the tools Rearrange, Reformat, Extract Method, Inline Method, code analyzers, hotkeys , etc.


2. No need to comment on each method, the code must be self-documented.


It should be noted that comments should explain the intentions and causes, and not the behavior of the code. Creating a comment, you must take responsibility for keeping the comment up to date.


3. Lines, colors, sizes must be in resources (xml)


No need to write:


 private static final String GREEN_BLUE = "#33c89b";  String.valueOf(" " + days + " ") 

Exceptions are files from the test, mock folders. Such files should not fall into the release version.


4. No need to pass context to RecyclerView.Adapter. context can be obtained from any view. The item on click in RecyclerView.Adapter should be changed through notifyItemChanged, and not by directly changing the views in the click processing method


  holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { item.buyNow(); bindViewHolder(holder, position); } }); 

In the onClick method, onClick is called bindViewHolder , which is NOT correct.


5. In RecyclerView.ViewHolder, instead of determining the position of an object in the list, call getAdapterPosition, and not pass the position from the onBindViewHolder method

To write correctly:


 private static class ItemHolder extends RecyclerView.ViewHolder { public ItemHolder(View itemView) { super(itemView); itemView.setOnClickListener(view -> { int position = getAdapterPosition(); ... }); } } 

Or, instead of a position, you can send a link to the source object with data


 itemView.setOnClickListener((v) -> { productClickListener.onProductClick(product); }); 

6. Use Quantity Strings (Plurals) for plurals . Instead of writing in the code logic for the selection of the correct end of words.

7. Do not save large Bitmap data in a bundle. Bundle size is limited

8. If you set the “id” in xml, then Android itself will restore the state of the standard View when turning.

Source: https://habr.com/ru/post/344470/


All Articles