Skip to content
Michael Bely edited this page Apr 29, 2024 · 56 revisions

Important

ВНИМАНИЕ!
ЭТОТ РАЗДЕЛ БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ!
РОАДМАП ПЕРЕЕХАЛ В NOTION

class
Шаблон для создания объектов, включающий переменные и функции и имеющий какое-то начальное состояние. Отличие от интерфейса: класс состоит из интерфейса и реализации, можно создать экземпляр

abstract class
Класс, у которого не реализован один или больше методов. Абстрактные классы нужны, чтобы создать иерархию родитель-наследник (абстрактный класс птица - наследники конкретные виды птиц: орлы, пингвины, страусы). Нельзя создать объект абстрактного класса, такой класс можно только унаследовать. Могут иметь абстрактные методы и свойства, которые не содержат реализацию и не хранят значения. Если класс наследуется от абстрактного класса, то он должен либо реализовать все его абстрактные методы и свойства, либо также быть абстрактным

interface
Абстрактный класс, у которого ни один метод не реализован, все они публичные и нет переменных класса. Интерфейсы нужны, чтобы задавать правила поведения (реализовать метод летать у некоторых видов птиц). Интерфейсы представляют контракт, который должен реализовать класс. Интерфейсы могут содержать объявления свойств и функций, а также их реализацию по умолчанию. Мы не можем напрямую создать объект интерфейса, так как интерфейс не поддерживает конструкторы и просто представляет шаблон, которому класс должен соответствовать. При применении интерфейса класс должен реализовать все его абстрактные методы и свойства, а также может предоставить свою реализацию для тех свойств и методов, которые уже имеют реализацию по умолчанию

cold flow
Cold observable. Создаются по запросу и выдают данные при подписке на них. Пример: retrofit

hot flow
Hot observable. Всегда активны и выдают данные независимо от того подписаны на них или нет. Пример: обработка жестов view

method signature
Сигнатура метода. Имя метода + параметры (порядок имеет значение). В сигнатуру не входит возвращаемое значение и бросаемые исключения

Методологии разработки

Agile
Гибкая методология разработки. Весь процесс работы над проектом делится на итерации — короткие циклы по две-три недели. Каждая итерация решает серию задач: анализ требований, проектирование, программирование, тестирование и документирование. По итогам каждой итерации команда анализирует результаты и меняет приоритеты для следующего цикла. В итоге за каждый цикл создается мини-продукт или отдельная часть, которая готова к самостоятельному запуску

Scrum
Одна из agile-методологий. Основу Scrum составляют короткие итерации или спринты, как правило, 2-3-х недельные. Перед началом спринта команда сама формирует список фич на итерацию, далее запускается спринт. После окончания спринта выполненные фичи заливаются на продакшн, а невыполненные — переносятся в другой спринт. Как правило, фичи, которые делаются во время спринта, не меняются: что было на старте спринта — должно быть сделано любой ценой к окончанию спринта. В Scrum задачи принято оценивать в Story points или в часах. Без оценки не получится сформировать спринт: ведь нам нужно знать, успеем ли мы сделать задачи за 2 недели

Kanban
Одна из agile-методологий. В основу данного принципа положен конвейерный метод производства, а также различные скорости выполнения отдельных технологических операций на производстве. Попробую объяснить на пальцах. При любом производстве есть основное производство («главный конвейер») и дополнительное производство («дополнительные конвейеры»). Темп выпуска конечных изделий задает главный конвейер, в то время как дополнительные конвейеры не могут ускорить темп выпуска изделия, но могут замедлить его, в случае несвоевременного выпуска требуемых деталей. Kanban хорошо работает в стартапах, не имеющих четкого плана, но активно работающих над разработкой

Сlasses and objects relationships

association
Ассоциация. Отношение, при котором объекты одного типа неким образом связаны с объектами другого типа. Например, объект одного типа содержит или использует объект другого типа. Например, игрок играет в определенной команде:

class Team
    
class Player {
    val team: Team
}

composition
Композиция определяет отношение HAS A, то есть отношение "имеет". Например, в класс автомобиля содержит объект класса электрического двигателя. При этом класс автомобиля полностью управляет жизненным циклом объекта двигателя. При уничтожении объекта автомобиля в области памяти вместе с ним будет уничтожен и объект двигателя. И в этом плане объект автомобиля является главным, а объект двигателя - зависимой

class ElectricEngine

class Car {
    val engine: ElectricEngine = ElectricEngine()
}

aggregation
Агрегация. Следует отличать от композиции. Предполагает то же отношение, но другую реализацию. При агрегации реализуется слабая связь, то есть в данном случае объекты Car и Engine будут равноправны. В конструктор Car передается ссылка на уже имеющийся объект Engine. И, как правило, определяется ссылка не на конкретный класс, а на абстрактный класс или интерфейс, что увеличивает гибкость программы

abstract class Engine

class Car(
    var engine: Engine
)

OOP

Object-Oriented Programming. Объектно-ориентированное программирование (ООП) - методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования

Encapsulation
Инкапсуляция - сокрытие реализации, один публичный метод и 9 приватных, которые что-то вычисляют под капотом. Контроль доступа к полям и методам объекта. Частью инкапсуляции является сокрытие данных через модификаторы доступа: public, private, protected

Polymorphism
Полиморфизм - свойство системы, позволяющее иметь множество реализаций одного интерфейса. Например, есть метод attack() и 3 класса реализуют его по разному: атака пушкой, мечом и копьем. При этом система ничего не знает про реализации и просто вызывает метод attack(), сигнатура которого одинакова для всех классов

Inheritance
Наследование - механизм, позволяющий одним классам наследовать свойства и поведение других классов для дальнейшего расширения или модификации

Development Principles

Принципы для разработки: KISS, DRY, YAGNI, BDUF, SOLID, APO и бритва Оккама

SOLID
Аббревиатура включающая 5 основных принципов ООП
• single responsibility - принцип единственной ответственности. Каждый объект, класс или метод должны отвечать за что-то одно. Не стоит нагружать классы большой логикой, иначе получится спагетти-код. Пример: RecyclerView (ItemAnimator, LayoutManager, Adapter, DiffUtil)
• open-closed - принцип открытости/закрытости. Классы должны быть открыты для расширения, но закрыты для модификации
• liskov substitution - принцип подстановки Лисков. Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Поведение методов наследника должно следовать принципам родителя, а не изменять их. То есть, дочерний класс переопределяя методы или переменные, не должен менять заложенную логику базового класса
• interface segregation - принцип разделения интерфейса. Объекты не должны зависеть от интерфейсов, которые они не используют. Создавайте узкоспециализированные интерфейсы, предназначенные для конкретного клиента. Клиенты не должны зависеть от интерфейсов, которые они не используют. Клиенты не должны реализовывать методы, которые им не нужно использовать. Интерфейс должен решать лишь какую-то одну задачу, поэтому всё, что выходит за рамки этой задачи, должно быть вынесено в другой интерфейс
• dependency inversion - принцип инверсии зависимостей. Модули верхних уровней не должны зависеть от модулей нижних уровней, оба должны зависеть от абстракций. Компоненты ПО должны иметь низкую связность и высокую согласованность. Пример: модуль data не зависит от presentation, ему все равно как данные отображаются

DRY
Don't Repeat Yourself. Не повторяйся. Дублирование кода – пустая трата времени и ресурсов. Вам придется поддерживать одну и ту же логику и тестировать код сразу в двух местах, причем если вы измените код в одном месте, его нужно будет изменить и в другом. В большинстве случаев дублирование кода происходит из-за незнания системы. Прежде чем что-либо писать, проявите прагматизм: осмотритесь. Возможно, эта функция где-то реализована. Возможно, эта бизнес-логика существует в другом месте. Повторное использование кода – всегда разумное решение

KISS
Keep It Simple, Stupid. Будь проще. Простые системы будут работать лучше и надежнее сложных. Не придумывайте к задаче более сложного решения, чем ей требуется. Иногда самое разумное решение оказывается и самым простым. Написание производительного, эффективного и простого кода – это прекрасно. Одна из самых распространенных ошибок нашего времени – использование новых инструментов исключительно из-за того, что они блестят. Разработчиков следует мотивировать использовать новейшие технологии не потому, что они новые, а потому что они подходят для работы

YAGNI
You Aren’t Gonna Need It. Вам это не понадобится. Если пишете код, будьте уверены, что он понадобится. Не пишите код, если думаете, что он пригодится позже. Принцип применим при рефакторинге. Если вы занимаетесь рефакторингом метода, класса или файла, не бойтесь удалять лишние методы. Даже если раньше они были полезны – теперь они не нужны. Может наступить день, когда они снова понадобятся – тогда вы сможете воспользоваться git-репозиторием, чтобы воскресить их из мертвых

BDUF
Big Design Up Front. Глобальное проектирование прежде всего. Прежде чем переходить к реализации, убедитесь, что все хорошо продумано. Составив план, вы избавите себя от необходимости раз за разом начинать с нуля.

APO
Avoid Premature Optimization. Избегайте преждевременной оптимизации. Эта практика побуждает разработчиков оптимизировать код до того, как необходимость этой оптимизации будет доказана. Прежде чем вы погрузитесь в детали реализации, убедитесь, что эти оптимизации действительно полезны. Пример – масштабирование. Вы не станете покупать 40 серверов из предположения, что ваше новое приложение станет очень популярным. Вы будете добавлять серверы по мере необходимости. Преждевременная оптимизация может привести к задержкам в коде и, следовательно, увеличит затраты времени на вывод функций на рынок

Occam's razor
Бритва Оккама. Не создавайте ненужных сущностей без необходимости. Будьте прагматичны — подумайте, нужны ли они, поскольку они могут в конечном итоге усложнить вашу кодовую базу

Clean Architecture

Архитектура, написанная с заботой. Код разделен на уровни, по структуре похоже на лук, с одним правилом зависимости: внутренний уровень не должен зависеть от каких-либо внешних уровней. Зависимости должны указываться внутри каждого уровня, чтобы не было зависимостей между уровнями (слоями)
• облегчить дальнейшую модификацию
• высокий уровень абстракции
• слабая связанность между частями кода
• легкая тестируемость

Data
Данные приложения

Domain
Бизнес-логика

Presentation
Пользовательский интерфейс


Repository
Объект, предоставляющий доступ к данным с возможностью выбора источника данных в зависимости от условий

Interactor
Реализует сценарии использования бизнес-объектов

UseCase
Действие, которое может совершить пользователь

Entities
Бизнес-объекты приложения

Architecture Patterns

MVP
Model-View-Presenter. Взаимодействие Presenter и View построено через интерфейс. View знает про Presenter и наоборот. Two-way binding

MVVM
Model-View-ViewModel. Взаимодействие ViewModel и View построено через LiveData. ViewModel ничего не знает про View. Unidirectional data flow

MVI
Model-View-Intent. Intent обрабатывает события от View и передает их в Model, она обрабатывает события и возвращает новую, готовую для отображения модель, которую отобразит View • однонаправленность - с данными работает только одна сущность, и мы знаем, кто изменяет данные, зачем и почему
• неизменяемость состояния - новое состояние складывается из предыдущего и какого-то действия. Изменить данные мы не можем, можем только получить новые
• удобство логирования и отладки - легко воспроизвести, где была ошибка, и собрать все условия (отследить в crashlytics текущий state и тот, что был до краша
• удобно работать с jetpack compose

Modularization

Есть несколько причин для многомодульности: время сборки, возможность повторного переиспользования кода, техническое обслуживание и разделение ответственности, легкое тестирование автономных функций, демонстрационные приложения, запустить одну изолированную функцию вместо запуска всего проекта после небольшого изменения

Incremental build
Запуск компилятора в рамках одного модуля только на тех файлах, которые изменились, либо имеют прямую зависимость от измененных файлов

Multithreading

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

Thread
Поток - небольшая часть процесса, последовательность инструкций, которая выполняется параллельно с другими потоками. Каждое приложение создает как минимум один поток: основной, который запускает функцию main(). Один поток – это одна единица исполнения кода. Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса

Асинхронность
Концепция программирования, когда события происходят независимо от главного потока и способ взаимодействия не является последовательным, а происходит через коллбэки

Параллельность
Тип вычислений, когда множество операций выполняются параллельно. Современные процессоры способствуют этому, так как имеют множество ядер

Синхронизация
• снятие блокировки потока должно происходить на том же потоке, которым она и была захвачена. Иначе поток зависнет навсегда

Design Patterns

Adapter
Позволяет объектам с несовместимыми интерфейсами работать вместе. Адаптер предусматривает создание класса-оболочки с требуемым интерфейсом

/**
 * Совместимый интерфейс и его реализация
 */
interface NickName {
    fun getNickName(person: Person): String
}
class NickNameImpl: NickName {
    override fun getNickName(person: Person): String = person.name
}

/**
 * Несовместимый интерфейс и его реализация
 */
interface FullName {
    fun getFullName(person: Person): String
}
class FullNameImp: FullName {
    override fun getFullName(person: Person): String = "${person.name} ${person.family}"
}

class Client(
    private val nickName: NickName
) {
    private val person = Person("John", "Marston")

    val name: String
        get() = nickName.getNickName(person)
}

class Adapter(
    private val fullName: FullName
): NickName {
 
    override fun getNickName(person: Person): String {
        return fullName.getFullName(person)
    }
}

val nickName: NickName = NickNameImpl()
val fullName: FullName = FullNameImp()
val adapter: NickName = Adapter(fullName)

val clientTarget = Client(nickName)
println(clientTarget.name) // output: John
val clientAdapter = Client(adapter)
println(clientAdapter.name) // output: John Marston

Decorator
Динамически добавляет новую функциональность объекту, используя класс-обертку. Пример: при добавлении нового ключа в HashMap, сообщать об этом

interface Name {
    fun getName(firstName: String): String
}

class SimpleName: Name {
    override fun getName(firstName: String): String {
        return firstName
    }
}

open class NameDecorator(
    protected var name: Name
): Name {
    override fun getName(firstName: String): String {
        return name.getName(firstName)
    }
}

class FullName(
    name: Name
): NameDecorator(name) {

    val lastName: String = "Marston"
 
    override fun getName(firstName: String): String {
        return name.getName(firstName) + " " + lastName
    }
}

val firstName: String = "John"

val name: Name = SimpleName()
println(name.getName(firstName)) // output: John

val nameDecorator: Name = NameDecorator(name)
println(nameDecorator.getName(firstName)) // output: John

val fullName: Name = FullName(name)
println(fullName.getName(firstName)) // output: John Marston

Facade
Оборачивает сложную подсистему более простым интерфейсом. Предоставляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы. Определяет интерфейс более высокого уровня, упрощающий использование подсистемы

interface Name {
    fun getName(): String
}
class NameImp: Name {
    override fun getName(): String = "John"
}

interface FullName {
    fun getFullName(): String
}
class FullNameImp: FullName {
    override fun getFullName(): String = "John Marston"
}

class FacadeName(
    private val name: Name,
    private val fullName: FullName
) {
    fun getName(): String = name.getName()

    fun getFullName(): String = fullName.getFullName()
}

val name: Name = NameImp()
val fullName: FullName = FullNameImp()
val facade = FacadeName(name, fullName)
println(facade.getName()) // output: John
println(facade.getFullName()) // output: John Marston

Composite
Компоновщик. Позволяет обращаться к отдельным объектам и к группам объектов одинаково. Объединяет объекты в древовидную структуру для представления иерархии от частного к целому

open class Person(
    private val name: String
) {
    open fun getName(): String = name
}

class John: Person("John")
class Arthur: Person("Arthur")

open class Composite(
    private val name: String
): Person(name) {

    private val persons: MutableList<Person> = mutableListOf()

    fun add(person: Person) {
        persons.add(person)
    }

    override fun getName(): String {
        return name + ", " + persons.joinToString(", ") { it.getName() }
    }
}

val composite = Composite("Sadie")
composite.add(John())
composite.add(Arthur())
println(composite.getName()) // output: Sadie, John, Arthur

Singleton
Когда нам необходим один экземппляр объекта и глобальная точка доступа к нему. Пример: класс Application

Dependency Injection

Dependency injection in Android

Внедрение зависимости — процесс предоставления внешней зависимости программному компоненту. Если нужно получить объект какого-либо класса, то нужно знать, как его создавать и придоставлять все необходимые ему для это зависимости. За это отвечает специальный компонент, который работает с графом зависимостей. Зависимость - объект, который необходимо получить с помощью компонента, а граф зависимостей — это набор всех таких объектов и связей между ними. Чтобы получить объект, нужно поместить его в граф и описать, каким образом можно получить экземпляр этого класса. DI рекомендуется использовать во всех реальных проектах. Можно описывать создание объектов один раз и использовать их везде в приложении, а также легко контролировать их lifecycle
• увеличивает декомпозицию кода
• уменьшает сваязанность разных участков кода
• дает лучную тестируемость и масштабируемость
• позволяет разделить процессы получения и создания зависимостей

koin
Легковесная библиотека для внедрения зависимостей, написанная на чистом kotlin
• узнаем об ошибке в runtime, а не в compile time
• не генерирует никакого кода, нет обработки аннотаций и рефлексии, более быстрый в compile time, более медленный в runtime
• все на delegates и dsl

Sorting algorithms

Основные виды сортировок и примеры их реализации

Bubble sort
Пузырьковая сортировка. Идем по массиву слева направо. Если текущий элемент больше следующего, меняем их местами. Делаем так, пока массив не будет отсортирован. Почти не применяется на практике из-за низкой эффективности: медленно работает на тестах, в которых маленькие элементы («черепахи») стоят в конце массива

Shaker sort
Шейкерная сортировка (сортировка перемешиванием, коктейльная сортировка). Отличается от пузырьковой двунаправленностью: алгоритм перемещается не строго слева направо, а сначала слева направо, затем справа налево

Comb sort
Сортировка расческой. Улучшение сортировки пузырьком. Идея состоит в том, чтобы «устранить» элементы с небольшими значения в конце массива, которые замедляют работу алгоритма. Если при пузырьковой и шейкерной сортировках при переборе массива сравниваются соседние элементы, то при «расчёсывании» сначала берётся достаточно большое расстояние между сравниваемыми значениями, а потом оно сужается вплоть до минимального

Time complexity

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

O(1) - константная сложность
Порядок роста O(1) означает, что вычислительная сложность алгоритма не зависит от размера входных данных. Следует помнить, однако, что единица в формуле не значит, что алгоритм выполняется за одну операцию или требует очень мало времени. Он может потребовать и микросекунду, и год. Важно то, что это время не зависит от входных данных

O(n) — линейная сложность
Такой сложностью обладает, например, алгоритм поиска наибольшего элемента в не отсортированном массиве. Нам придётся пройтись по всем n элементам массива, чтобы понять, какой из них максимальный

O(log n) — логарифмическая сложность
Простейший пример — бинарный поиск. Если массив отсортирован, мы можем проверить, есть ли в нём какое-то конкретное значение, методом деления пополам. Проверим средний элемент, если он больше искомого, то отбросим вторую половину массива — там его точно нет. Если же меньше, то наоборот — отбросим начальную половину. И так будем продолжать делить пополам, в итоге проверим log n элементов

O(n2) — квадратичная сложность
Такую сложность имеет, например, алгоритм сортировки вставками. В канонической реализации он представляет из себя два вложенных цикла: один, чтобы проходить по всему массиву, а второй, чтобы находить место очередному элементу в уже отсортированной части. Таким образом, количество операций будет зависеть от размера массива как n * n, т. е. n2

Memory Leaks

Memory leak
Объект можно назвать утечкой памяти, если он продолжает существовать в памяти даже после того, как на него потеряны все ссылки

LeakCanary
Библиотека для обнаружения утечек памяти в Android. Предназначена для выявления ANR и OutOfMemoryError. Как работает: делает dump памяти и изучает его, обнаруживая объекты, которые должны были быть собраны сборщиком мусора

Tools

Confluence
Продвинутый wiki-движок от компании Atlassian. Организованный внутренний интернет-портал для документации и справочной информации

Jira
Система для управления задачами

Swagger
Разрабатывать и документировать API. Фреймворк для спецификации REST. Позволяет интерактивно просматривать и отправлять запросы

TeamCity
CI/CD билд-сервер для непрерывной интеграции от JetBrains

Jenkins
Среда для непрерывной интеграции и развертывания


XML
Extensible Markup Language. Язык разметки, используемый для описания данных. В Android в основном используется для описания данных, связанных с UI

CI/CD
Непрерывная интеграция (Continuous Integration, CI) и непрерывная поставка (Continuous Delivery, CD) представляют собой культуру, набор принципов и практик, которые позволяют разработчикам чаще и надежнее развертывать изменения программного обеспечения. Цель CI — обеспечить последовательный и автоматизированный способ сборки, упаковки и тестирования приложений. Непрерывная поставка начинается там, где заканчивается непрерывная интеграция. Она автоматизирует развертывание приложений в различные окружения

Recursive functions
Рекурсия - функция, которая вызывает саму себя

fun factorial(n: Int): Double {
    return if (n < 2) 1.0 else n * factorial(n - 1)
}

Tail recursive functions
Хвостовая рекурсия - рекурсия, при которой любой рекурсивный вызов является последней операцией перед возвратом из функции

tailrec fun fibonacci (n: Int, a: Long, b: Long): Long {
    return if (n == 0) b else fibonacci (n - 1, a + b, a)
}

Regular Expressions
Формальный язык поиска и осуществления манипуляций с подстроками в тексте, основанный на использовании метасимволов. Механизм для поиска и замены текста с использованием шаблонов

Семантическое версионирование
Формат версий X.Y.Z (мажорная, минорная, патч)