📜 ⬆️ ⬇️

Understanding Clean Code in Android

As an introduction, I would like to recall Uncle Bob 's quote.
You are reading this article for two reasons. The first is you are a programmer, the second is you want to be a better programmer.
Imagine that you are in the library and are looking for some books. If the library is sorted, has categories of books, then you quickly find the one you need. In addition, the cool interior design and architecture will make your stay in this library quite comfortable for you.

As with writing books, if you want to create something great, then you need to know how to write and how to organize your code. If you have team members or someone else who has your (outdated) code, they just need to see the names of variables or packages or classes, and they will immediately understand. They do not need to say "E ** l" I have this code and start it again from scratch.

What is Clean Code?



')
Transfer
Dear programmer:

When I wrote this code, only God and I knew how it works!
Now only God knows that!

Before you try to optimize this routine and it will not lead to success (most likely), please increase the time elapsed counter to warn the next developer

Total hours spent: 567

As you can see, it’s not enough to finish development faster. If in the future this code cannot be sorted out, it will also become a technical duty.

Your code has the status “Clean” if every member of your team can understand it. Clean code can be read and improved by any developer other than the original author. With understanding comes readability, mutability, extensibility and maintainability.

Should I take care of this?


The reason why you should care about the purity of your code is that you describe the course of your thoughts to other developers. That is why you should take care that your code is more elegant, simple and readable.

Signs of Clean Code



The difference between a good programmer and a professional is that a professional programmer understands that clear code is paramount. The professional uses this power to write code that everyone understands - Robert C. Martin

Write lucid names


Choosing a good name may take a long time, but later it will save more. The name of a variable, function, class must answer all significant questions. It should tell you why it exists, why it is needed and how to use it. If the name requires a comment, the name does not reveal its purpose.

Simple example

// Bad variables naming var a = 0 // user ages var w = 0 // user weight var h = 0 // user height // Bad functions naming fun age() fun weight() fun height() // Bad classes naming to get user data class UserInfo() // Best practices varibales naming var userAge = 0 var userWeight = 0 var userHeight = 0 // Best practices functions naming fun setUserAge() fun setUserWeight() fun setUserHeight() // Best practices classes naming to get user data class Users() 

Class names


Classes and objects must be nouns, for example ustomer, WikiPage, Account, and AddressParser. Avoid words like Manager, Processor, Data, or Info. Remember also that the class name should not be a verb.

Method Names


Method names must be verbs, for example postPayment, deletePage or save. Access modifiers, predicates should be named for their value and with the prefix get, set and according to the JavaBean standard.

Before we continue take a short break, stock up on coffee and cookies.



Okay, now let's move on to the SOLID principles.

Write code adhering to SOLID principles


These principles were developed by Uncle Bob, SOLID is an abbreviation describing a set of principles for writing good code.

The principle of common responsibility (S)


This means that each class must bear only one responsibility. There should never be more than one reason for a class change. Do not add everything to your class, simply because you can do it. Break large classes into smaller ones and avoid God Classes.

Example:

We have a RecyclerView.Adapter with business logic inside onBindViewHolder

 class MyAdapter(val friendList: List<FriendListData.Friend>) : RecyclerView.Adapter<CountryAdapter.MyViewHolder>() { inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { var name: TextView = view.findViewById(R.id.text1) var popText: TextView = view.findViewById(R.id.text2) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val friend = friendList[position] val status = if(friend.maritalStatus == "Married") { "Sold out" } else { "Available" } holder.name.text = friend.name holder.popText.text = friend.email holder.status.text = status } override fun getItemCount(): Int { return friendList.size } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_friendlist, parent, false) return MyViewHolder(view) } } 

This makes RecyclerView.Adapter not a single responsibility, because it contains the business logic inside onBindViewHolder. This method is only responsible for inserting data into the view.

The principle of openness / closeness (O)


Software entities must be open for expansion, but closed for modification. This means that if you are developing class A and your colleagues will want to change the function within this class. They can easily do this by extending this class without changing the class itself.
A simple example is the RecyclerView.Adapter class. You can easily extend it and create your own adapter with non-standard behavior without modifying the RecyclerView.Adapter itself.

 class FriendListAdapter(val friendList: List<FriendListData.Friend>) : RecyclerView.Adapter<CountryAdapter.MyViewHolder>() { inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { var name: TextView = view.findViewById(R.id.text1) var popText: TextView = view.findViewById(R.id.text2) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val friend = friendList[position] holder.name.text = friend.name holder.popText.text = friend.email } override fun getItemCount(): Int { return friendList.size } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_friendlist, parent, false) return MyViewHolder(view) } } 

Barbara Liskov substitution principle (L)


The child class must complement the parent, not change it. This means that the subclass must override the parent methods that do not violate the functionality of this parent class. For example, we create a class interface that has an onClick () listener and then you apply a listener in MyActivity and give it a Toast action when onClick () is called.

 interface ClickListener { fun onClick() } class MyActivity: AppCompatActivity(), ClickListener { //........ override fun onClick() { // Do the magic here toast("OK button clicked") } } 

Interface separation principle


This principle states that the client should not be dependent on methods that it does not use.
This means that if you want to write class A and add to it the functionality of another class B. There is no need to redefine all classes A within class B.

Example: in our activity, we need to implement the SearchView.OnQueryTextListener (), but we only need the onQuerySubmit () method.

 mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{ override fun onQueryTextSubmit(query: String?): Boolean { // Only need this method return true } override fun onQueryTextChange(query: String?): Boolean { // We don't need to implement this method return false } }) 

How do we do this? Easy! Just create a callback and a class that extends SearchView.OnQueryTextListener ()

 interface SearchViewQueryTextCallback { fun onQueryTextSubmit(query: String?) } class SearchViewQueryTextListener(val callback: SearchViewQueryTextCallback): SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { callback.onQueryTextSubmit(query) return true } override fun onQueryTextChange(query: String?): Boolean { return false } } 

And so we add it to our view.

 val listener = SearchViewQueryTextListener( object : SearchViewQueryTextCallback { override fun onQueryTextSubmit(query: String?) { // Do the magic here } } ) mSearchView.setOnQueryTextListener(listener) 

Or so, using the Extension Function in Kotlin

 interface SearchViewQueryTextCallback { fun onQueryTextSubmit(query: String?) } fun SearchView.setupQueryTextSubmit (callback: SearchViewQueryTextCallback) { setOnQueryTextListener(object : SearchView.OnQueryTextListener{ override fun onQueryTextSubmit(query: String?): Boolean { callback.onQueryTextSubmit(query) return true } override fun onQueryTextChange(query: String?): Boolean { return false } }) } 

 val listener = object : SearchViewQueryTextCallback { override fun onQueryTextSubmit(query: String?) { // Do the magic here } } mSearchView.setupQueryTextSubmit(listener) 

Dependency Inversion Principle


Dependence on abstractions, without dependence on something concrete.
The definition of the inversion of dependencies on Uncle Bob consists of two concepts.

The modules of the upper levels should not depend on the modules of the lower levels. Both must be tied to abstractions. Abstractions should not depend on the details. Details must depend on abstractions. High-level modules that implement complex logic should be easily reusable with no changes in lower-level modules. To do this, you need to introduce an abstraction that separates the modules of the upper and lower levels from each other.

A simple example of this MVP pattern. You have an interface object that helps you communicate with specific classes. What is meant is that the UI (Activity / Fragment) classes do not need to know the actual implementation of the presenter methods. Thus, if you make changes inside the presenter, the UI classes do not have to worry about these changes.

Let's take a look at an example.

 interface UserActionListener { fun getUserData() } class UserPresenter : UserActionListener() { // ..... override fun getUserData() { val userLoginData = gson.fromJson(session.getUserLogin(), DataLogin::class.java) } // ..... } 

And now on the activation

 class UserActivity : AppCompatActivity() { //..... val presenter = UserPresenter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Activity doesn't need to know how presenter works // for fetching data, it just know how to call the functions // So, if you add method inside presenter, it won't break the UI. // even the UI doesn't call the method. presenter.getUserData() } //.... } 

Thus, we create an interface that abstracts the presenter implementation, and our view class retains a reference to the PresenterInterface.

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


All Articles