Разбираемся с Clean Code в Android

Original author: Yoga C. Pranata
  • Translation
В качестве вступления хотелось бы вспомнить цитату дядюшки Боба
Вы читаете эту статью по двум причинам. Первая — Вы программист, вторая — Вы хотите быть лучшим программистом.
Представьте, что Вы находитесь в библиотеке и ищете какие-то книги. Если библиотека сортирована, имеет категории книг, то вы быстро отыщите нужную Вам. Кроме того, крутой дизайн интерьера и архитектура сделает пребывание в этой библиотеке довольно комфортным для Вас.

Как и при написании книг, если вы хотите создать что-то великое, то вы должны знать как писать и как организовывать свой код. Если у вас есть члены команды или кто-то еще, у кого есть Ваш (устаревший) код, им просто нужно увидеть имена переменных или пакеты или классы, и они сразу поймут. Им не нужно говорить «Е**л» я этот код и начинать его снова с нуля.

Что такое «Чистый Код»?




Перевод
Дорогой программист:

Когда я писал этот код, только Бог и я знал как он работает!
Теперь только Бог знает это!

Перед тем как ты попытаешься оптимизировать эту рутину и это не приведет к успеху (наиболее вероятно), пожалуйста увеличь счетчик затраченного времени для предостережения следующего разработчика

Всего потрачено часов: 567

Как Вы можете видеть, недостаточно закончить разработку быстрее. Если в будущем в этом коде не смогут разобраться, то это так же станет техническим долгом.

Ваш код имеет состояние «Чистый», если его может понять каждый член Вашей команды. Чистый код может быть прочитан и улучшен любым разработчиком, отличным от первоначального автора. С пониманием приходит читаемость, изменяемость, расширяемость и ремонтопригодность.

Я должен заботиться об этом?


Причина, по которой Вы должны заботиться о чистоте своего кода, заключается в том, что Вы описываете ход своих мыслей другим разработчикам. Именно поэтому Вы должны заботиться о том, что Ваш код был более элегантным, простым и читаемым.

Признаки Чистого кода


  • Ваш код должен быть элегантным: Ваш код должен заставить Вас улыбаться, как хорошо сделанная музыкальная шкатулка или хорошо спроектированный автомобиль
  • Ваш код должен быть сфокусирован: каждая функция, каждый класс, каждый модуль реализует одну задачу, которая остается полностью не отвлеченным и незагрязненным окружающими деталями.
  • Код не содержит дубликатов
  • Успешно проходят все тесты
  • Минимизировано количество сущностей, методов, классов и так далее

Отличие между хорошим программистом и профессионалом в том, что профессиональный программист понимает, что понятность кода первостепенна. Профессионал использует эту силу для написания кода, который понятен всем — Robert C. Martin

Пишите осознанные имена


Выбор хорошего имени, может занять много времени, но в последствии оно сэкономит больше. Имя переменной, функции, класса должно отвечать на все значимые вопросы. Оно должно рассказать Вам, почему оно существует, зачем оно нужно и как его использовать. Если имя требует комментария, то имя не раскрывает своего предназначения

Простой пример

// 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()

Имена классов


Классы и объекты должны быть существительными, например Сustomer, WikiPage, Account, and AddressParser. Избегайте таких слов как Manager, Processor, Data, или Info. Помните так же, что имя класса не должно быть глаголом.

Имена методов


Имена методов же должны быть глаголами, например postPayment, deletePage или save. Модификаторы доступа, предикаты должны быть названы по их значению и с префиксом get, set и согласно стандарту JavaBean.

Перед тем, как мы продолжим сделайте небольшой перерыв, запаситесь кофе и печеньками



Окей, теперь перейдем к SOLID принципам

Пишите код, придерживаясь SOLID принципов


Эти принципы были разработаны дядюшкой Бобом, SOLID это аббревиатура, описывающая набор принципов, предназначенных для написания хорошего кода.

Принцип единственную ответственности  (S)


Это означает, что каждый класс должен нести только одну ответственность. Никогда не должно быть более одной причины для изменения класса. Не надо добавлять все в свой класс, просто потому что Вы можете это сделать. Разбивайте большие классы на более маленькие и избегайте God Classes.

Пример:

У нас есть RecyclerView.Adapter с бизнес логикой внутри 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)
    }
}

Это делает RecyclerView.Adapter не имеющим единственную ответственность, потому что он содержит бизнес логику внутри onBindViewHolder. Этот метод отвечает только за вставку данных во view.

Принцип открытости/закрытости (О)


Программные сущности должны быть открыты для расширения, но закрыты для модификации. Это означает, что если Вы разрабатываете класс А и ваши коллеги захотят изменить функцию внутри этого класса. Они смогут легко это сделать, расширив этот класс без изменения самого класса.
Простой пример класс RecyclerView.Adapter. Вы можете с легкостью расширить его и создать свой собственный адаптер с нестандартным поведением без модификации самого RecyclerView.Adapter.

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

Принцип подстановки Барбары Лисков (L)


Дочерний класс должен дополнять родительский, а не изменять его. Это означает, что подкласс должен переопределять методы родительского, которые не нарушают функциональность этого родительского класса. Например мы создаем интерфейс класса, который имеет onClick() слушатель а затем вы применяете слушатель в MyActivity и даете ему действие Toast, когда вызывается onClick ().

interface ClickListener {
    fun onClick()
}

class MyActivity: AppCompatActivity(), ClickListener {

    //........
    override fun onClick() {
        // Do the magic here
        toast("OK button clicked")
    }

}

Принцип разделения интерфейса


Этот принцип гласит, что клиент не должен быть зависим от методов, которые он не использует.
Это означает, что если Вы хотите написать класс А и добавить в него функциональность другого класса В. Нет необходимости переопределять все классы А внутри класса В.

Пример: в нашей активити, нам нужно реализовать SearchView.OnQueryTextListener(), но нам нужен только onQuerySubmit() метод.

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

Как мы это сделаем? Легко! Просто создадим callback и класс, расширяющий 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
    }
}

И вот так мы добавим это к нашей view

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

Или так, используя Extension Function в 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)

Принцип инверсии зависимостей


Зависимость на абстракциях, без зависимости на что-то конкретное.
Определение инверсии зависимостей от дядюшки Боба состоит из двух понятий.

Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба должны быть завязаны на абстракциях. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Модули высоких уровней, которые реализуют комплексную логику, должны быть легко переиспользуемы без изменений в модулях нижнего уровня. Для этого Вам нужно ввести абстракцию, которая отделяет модули верхнего и нижнего уровней друг от друга.

Простой пример этого MVP паттерн. У Вас есть объект интерфейсов, который помогает Вам общаться с конкретными классами. Что имеется ввиду — классам UI (Activity/Fragment) не нужно знать фактическую реализацию методов презентера. Таким образом, если Вы делаете изменения внутри презентера, UI классы не должны волновать эти изменения

Давайте взглянем на пример

interface UserActionListener {
    fun getUserData()
}

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

А теперь на активити

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()
   }
   
   //....
}

Таким образом мы создаем интерфейс, который абстрагирует реализацию презентатора, и наш класс view сохраняет ссылку на PresenterInterface.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 3

    0
    Как по мне, принцип Лисков лучше всего показывать на антипримерах.
    Например интерфес List в Java и его реализации в методах Collections.emptyList() и Collections.singletonList().
    Еще в самом Андроиде часто встречал нарушение этого принципа, но с ходу не найду.
    А пример с OnClickListener'ом как-то ниочем.
      0
      А ещё в AndroidStudio можно установить плагин SonarLint. Он способен понизить самооценку разработчика любого уровня:)
        0

        Не пойму пример с RecyclerView правильный или неправильный? Тогда надо во ViewHolder создать метод bind() и там сетить? Это имелось ввиду?

        Only users with full accounts can post comments. Log in, please.