Цель - облегчить чтение, тестирование и поддержку исходного кода командой разработчиков.
При составлении документа использовались следующие источники:
- Книга "Чистый код. Создание анализ и рефакторинг | Мартин Роберт К."
- Сайт Refactoring Guru
class MyParser {
func parseA(yyyymmddhhmmss: String) -> Date {
return f("yyyy-MM-dd'T'HH:mm:ssZ").date(from: yyyymmddhhmmss)!
}
func parseB(yyyymmdd: String) -> Date {
return f("yyyy-MM-dd").date(from: yyyymmdd)!
}
func f(ff: String) -> DateFormatter {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = ff
return df
}
}
- Суффиксы и префиксы "My", "Custom", "A", "B" и подобные им не несут смысловой нагрузки, как и перечисления через "1", "2", "one", "two".
- Трудночитаемые и труднопроизносимые названия, такие как "yyyymmddhhmmss", усложняют чтение и написание кода.
- Если название функции не отражает ее назначение, то без просмотра исходника невозможно понять, что она делает. То же самое относится к классам и переменным.
class DateParser {
func parseIsoDateTime(isoDateTime: String) -> Date {
return dateFormatter("yyyy-MM-dd'T'HH:mm:ssZ").date(from: isoDateTime)!
}
func parseIsoDate(isoDate: String) -> Date {
return dateFormatter("yyyy-MM-dd").date(from: isoDate)!
}
func dateFormatter(dateFormat: String) -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = dateFormat
return dateFormatter
}
}
let c = await findCars();
for (let i = 0; i < c.length; i++) {
let cc = c[i];
let o = await findOrders(cc);
for (let j = 0; o.length; j++) {
let oo = o[j];
// Do some stuff with car and order.
// Stop... what is a `c`?
// ...and, what is a `oo`?
}
}
- Чтобы прочитать код с сокращениями, нужно их изучить и запомнить. Это повышает нагрузку на программиста.
- Сокращенные названия индексов и других переменных легко перепутать, что будет трудно отладить.
- Исключение - известные аббревиатуры.
let cars = await findCars();
for (let carIndex = 0; carIndex < cars.length; carIndex++) {
let car = cars[carIndex];
let orders = await findOrders(car);
for (let orderIndex = 0; orderIndex < orders.length; orderIndex++) {
let order = orders[orderIndex];
// Do some stuff with car and order.
}
}
fun showContract(trade: Contract) {
showDealName(trade)
showDealDate(trade)
}
fun showDealName(cotract: Contract) {
this.dealName.text = contract.name
}
- Визуально кажется, что производится работа с разными типами данных. Особенно в языках с динамической типизацией.
- Затруднен поиск по исходному коду. Нужно искать несколько раз с разными названиями.
fun showContract(contract: Contract) {
showContractName(contract)
showContractDate(contract)
}
fun showContractName(contract: Contract) {
this.contractName.text = contract.name
}
function showItemsInPager(items, onPageChangeListener) {
this.pager.setItems(items);
setTimeout(
() => { this.pager.setOnPageChangeListener(onPageChangeListener) },
300
);
}
function potentialEnergy(mass, height) {
return mass * height * 9.81;
}
- По не именованному значению в коде трудно понять его смысл и назначение.
- Если значение встречается несколько раз, то поменять его придется во всех местах.
const PAGER_LISTENER_DELAY_TO_PREVENT_INITIAL_TRIGGERING = 300;
function showItemsInPager(items, onPageChangeListener) {
this.pager.setItems(items);
setTimeout(
() => { this.pager.setOnPageChangeListener(onPageChangeListener) },
PAGER_LISTENER_DELAY_TO_PREVENT_INITIAL_TRIGGERING
);
}
const earthGravityAcceleration = 9.81;
function potentialEnergy(mass, height) {
return mass * height * earthGravityAcceleration;
}
def parse_product_from_page(page_title)
return self.build_product(
page_title.split('@')[0].split('-')[0].strip,
page_title.split('@')[1]
)
end
- Трудно понять смысл вычислений без просмотра аргументов функции.
- Часть одинакового исходного кода выполняется несколько раз.
def parse_product_from_page(page_title)
title_parts = page_title.split('@')
product_title = title_parts[0].split('-')[0].strip
shop_name = title_parts[1]
self.build_product(product_title, shop_name)
end
def save_film(film)
if self.user.roles.admin.any? || film.creator_id == self.user.id
self.repository.save(film)
end
end
- Трудно понять смысл проверки без детального изучения каждого условия.
- Код разросся по горизонтали.
def save_film(film)
is_admin = self.user.roles.admin.any?
is_creator = film.creator_id == self.user.id
if is_admin || is_creator
self.repository.save(film)
end
end
def save_film(film)
if self.is_admin || self.is_creator(film)
self.repository.save(film)
end
end
def is_admin
self.user.roles.admin.any?
end
def is_creator(film)
film.creator_id == self.user.id
end
public class ProfileScreen {
\
public static void open(String firstName,
String lastName,
Gender gender,
Country country,
String city,
String street,
String house) {
// Open new screen using SDK
}
}
public class HomeScreen {
public void onProfileButtonClicked() {
String firstName = firstNameView.getText();
String lastName = lastNameView.getText();
// Get other values from views
ProfileScreen.open(
firstName, lastName, gender,
country, city, street, house
);
}
}
- Легко запутаться в порядке аргументов и передать их на неправильных местах.
- Трудно отформатировать функцию, приходится делать переносы в описании аргументов.
- При необходимости добавить новую информацию о сущности, нужно будет рефакторить цепочку вызова функций.
public class ProfileScreen {
public static void open(PersonalInfo personalInfo, Address address) {
// Open new screen using SDK
}
}
public class HomeScreen {
public void onScreenButtonClicked() {
String firstName = firstNameView.getText();
String lastName = lastNameView.getText();
// Get other values from views
PersonalInfo personalInfo = new PersonalInfo(firstName, lastName, gender);
Address address = new Address(country, city, street, house);
ProfileScreen.open(personalInfo, address);
}
}
public class ImageUtils {
public static File ensureCompatibility(
File imageFile,
String userAgent,
boolean useCache,
boolean debugOutput,
boolean removeTempFiles) {
// Image conversion logic
}
}
public void main() {
ImageUtils.ensureCompatibility(
new File("../storage/image.webp"),
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
false,
true,
true
);
}
- При вызове функции трудно определить, за что отвечают похожие по типу неименованные аргументы.
public class ImageCompatibilityService {
private boolean useCache = true;
private boolean debugOutput = false;
private boolean removeTempFiles = true;
// Setters
public File convert(File imageFile, String userAgent) {
// Image conversion logic
}
}
public void main() {
ImageCompatibilityService converter = new ImageCompatibilityService()
.setUseCache(false)
.setDebugOutput(true)
.setRemoveTempFiles(true)
converter.convert(
new File("../storage/image.webp"),
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
);
}
class ImageUtils
def self.ensure_compatibility(image_file:,
user_agent:,
use_cache: true,
debug_output: false,
remove_temp_files: true)
# Image conversion logic
end
end
# Somewhere in the code
ImageUtils.ensure_compatibility(
image_file: File.new('../storage/image.webp'),
user_agent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
use_cache: false,
debug_output: true,
remove_temp_files: true
);
func coloriseOrderStatus(order: Order) {
var colorHex: String? = nil
if order.creationDate != nil {
colorHex = "#ff0000"
} else if order.processingStartDate != nil {
colorHex = "#00ff00"
} else if order.deliveryStartDate != nil {
colorHex = "#0000ff"
} else {
colorHex = "#000000"
}
statusView.backgroundColor = getUIColorByHex(colorHex)
}
func getUIColorByHex(hex: String) -> UIColor {
// Create UIColor by hex
}
- В коде одной функции смешано несколько задач, среди которых не видно алгоритма и главного действия.
func coloriseOrderStatus(order: Order) {
let status = getOrderStatus(order)
let colorHex = getOrderColorHexByStatus(status)
let color = getUIColorByHex(color)
textView.backgroudColor = color
}
func getOrderStatus(order: Order) -> OrderStatus {
if order.creationDate != nil {
return .created
} else if order.processingStartDate != nil {
return .processing
} else if order.deliveryStartDate != nil {
return .delivery
} else {
return .unknown
}
}
func getOrderColorHexByStatus(status: OrderStatus) -> String {
switch status {
case .created:
return "#ff0000"
case .processing:
return "#00ff00"
case .delivery:
return "0000ff"
case .unknown:
return "#000000"
}
}
func getUIColorByHex(hex: String) -> UIColor {
// Create UIColor by hex
}
public Product parseProductFromPage(Page page) {
String name = null;
Element titleElement = page.findElementByTag("title");
if (titleElement != null) {
String title = titleElement.getText();
name = title.split("-")[0].trim();
}
String description = findDescriptionOnPage(page);
Integer rating = findRatingOnPage(page);
return new Product(name, description, rating);
}
- Чтение кода с разным уровнем абстракции заставляет мозг переключаться между уровнями, неявно выстраивая новый уровень абстракции самостоятельно.
public Product parseProductFromPage(Page page) {
String name = findNameOnPage(page);
String description = findDescriptionOnPage(page);
Integer rating = findRatingOnPage(page);
return new Product(name, description, rating);
}
public String findNameOnPage(Page page) {
Element titleElement = page.findElementByTag("title");
if (titleElement == null) {
return null;
}
String title = titleElement.getText();
String name = title.split("-")[0].trim();
return name;
}
class Table
attr_accessor :items
def add_items(items)
if self.items.nil?
self.items = items
else
self.items.push(*items)
end
end
end
items = [1, 2, 3]
table = Table.new
table.add_items(items)
# Somewhere in the other code
items.push(4, 5, 6) # Oops, items added to the table.
- Так как ссылка на один и тот же объект захвачена разными контекстами, то модификация объекта неявным образом влияет на работу каждого контекста.
class Table
attr_accessor :items
def add_items(items)
if self.items.nil?
self.items = []
end
self.items.push(*items)
end
end
items = [1, 2, 3]
table = Table.new
table.add_items(items)
# Somewhere in the other code
items.push(4, 5, 6) # This is OK
func runTask(task: Task) {
if !task.isRunning() {
task.run()
}
}
// Working with already running task
let task = getTask()
runTask(task) // Why the task didn't run if i called the method????
- Невозможно понять весь алгоритм, если не посмотреть реализацию всех функций.
func runTaskIfNotRunning(task: Task) {
if !task.isRunning() {
task.run()
}
}
// I know that task will not be runned if already runned.
let task = getTask()
runTaskIfNotRunning(task);
public Integer getTotalAmount() {
if (this.isArchived()) {
return null;
} else {
if (this.cachedTotalAmount != null) {
return this.cachedTotalAmount;
} else {
if (this.isEnoughData()) {
return this.calculateTotalAmount();
} else {
return this.getDefaultTotalAmount();
}
}
}
}
- Среди множества вложенных условий трудно выделить нормальный ход выполнения кода.
- Выделите все проверки специальных или граничных случаев в отдельные проверки и поместите их перед основным кодом, чтобы получить линейную структуру без вложенностей.
public Integer getTotalAmount() {
if (this.isArchived()) {
return null;
}
if (this.cachedTotalAmount != null) {
return this.cachedTotalAmount;
}
if (!this.isEnoughData()) {
return this.getDefaultTotalAmount();
}
return this.calculateTotalAmount();
}
class ProjectPage {
loadProject() {
this.projectUseCase
.getById(this.projectId)
.then((project) => {
this.showProject(project);
this.showMessage('Loaded', true);
})
.catch(() => {
this.showMessage('Loading error', false);
});
}
showMessage(messageText, isError) {
if (isError) {
// Show error message, for example via SnackBar
} else {
// Show success message, for example via Toast
}
}
}
- Флаг в аргументах вносит избыточное ветвление и делает функцию ответственной за несколько вещей.
class ProjectPage {
loadProject() {
this.projectUseCase
.getById(this.projectId)
.then((project) => {
this.showProject(project);
this.showSuccessMessage('Loaded');
})
.catch(() => {
this.showErrorMessage('Loading error');
});
}
showSuccessMessage(messageText) {
// Show success message, for example via Toast
}
showErrorMessage(messageText) {
// Show error message, for example via SnackBar
}
}
class Info(
var id: Integer? = null,
var name: String? = null,
var status: String? = null,
var description: String? = null,
var price: Integer? = null
)
val product = Info()
product.id = 1
product.name = "Spoon"
product.description = "Material - Silver"
product.price = 3
val cart = Info()
cart.id = 1
cart.status = CartStatus.CHECKOUT.value
cart.price = 10
- С течением времени God Object бесконечно разрастается.
- Приходится работать с лишними полями.
class Product(
var id: Integer? = null,
var name: String? = null,
var description: String = null,
val price: Integer? = null
)
class Cart(
var id: Integer? = null,
var status: String? = null,
var price: String? = null
)
val product = Product()
product.id = 1
product.name = "Spoon"
product.description = "Material - Silver"
product.price = 3
val cart = Cart()
cart.id = 1
cart.status = CartStatus.CHECKOUT.value
cart.price = 10
class City
attr_accessor :city_id, :city_name, :region_id, :region_name
end
city = City.new
city.city_id = 1
city.city_name = "Novosibirsk"
city.region_id = 1
city.region_name = "Novosibirsk region"
- Невозможно независимо переиспользовать некоторые поля.
- Код разрастается из-за дублирования суффиксов.
class City
attr_accessor :id, :name, :region
end
class Region
attr_accessor :id, :name
end
region = Region.nnew
region.id = 1
region.name = "Novosibirsk region"
city = City.new
city.id = 1
city.name = "Novosibirsk"
city.region = region
public class CarCreator {
private apiService = ApiService()
public func create(car: Car) -> Car {
let json = generateCarJson(car)
let createdCar = apiService.createCar(car)
return createdCar
}
public func generateCarJson(car: Car) -> String {
// Some logic of json generation
}
}
let car = Car()
let carCreator = CarCreator()
carCreator.generateCarJson(car) // Why do I need this function?
- Среда разработки при автодополнении показывает методы, которые не нужны программисту, который использует класс.
public class CarCreator {
private apiService = ApiService()
public func create(car: Car) -> Car {
let json = generateCarJson(car)
let createdCar = apiService.createCar(car)
return createdCar
}
private func generateCarJson(car: Car) -> String {
// Some logic of json generation
}
}
let car = Car()
let carCreator = CarCreator()
carCreator.create(car) // I see only necessary functions
public class Category {
private int id;
private String name;
private List<SubCategory> subCategories;
}
public class SubCategory {
private int id;
private String name;
private List<SubSubCategory> subSubCategories;
}
public class SubSubCategory {
private int id;
private String name;
private List<Service> services;
}
public class Service {
private int id;
private String name;
}
- Трудно придумать хорошие отличимые названия для вложенных друг в друга классов.
- При появлении нового уровня вложенности нужно создавать новый класс.
- Если конечный объект может находиться на любом уровне вложенности, придется добавлять его в класс каждого уровня.
public class Category {
private int id;
private String name;
private List<Services> services;
private List<Category> categories;
}
public class Service {
private int id;
private String name;
}
class ProductScreen : Screen {
fun showProduct(product: Product) {
this.price.text = this.formatMoney(product.price)
}
}
class ServiceScreen : Screen {
fun showService(service: Service) {
this.price.text = this.formatMoney(service.price)
}
}
open class Screen : SomeSdkClass {
protected fun formatMoney(money: Money): String {
// Some formatting logic
return "1.234.567,89 $"
}
}
class CartSummaryCell : Cell {
fun showSummary(cart: Cart) {
// Whait! I don't have a formatMoney method here...
this.amount.text = ""
}
}
- Невозможно использовать метод из базового класса в другом дереве наследования.
class MoneyFormatter {
fun format(Money money): String {
// Some formatting logic
return "1.234.567,89 $"
}
}
class ProductScreen : Screen {
fun showProduct(product: Product) {
this.price.text = MoneyFormatter().format(product.price)
}
}
class ServiceScreen : Screen {
fun showService(service: Service) {
this.price.text = MoneyFormatter().format(service.price)
}
}
class CartSummaryCell : Cell {
fun showSummary(cart: Cart) {
this.amount.text = MoneyFormatter().format(cart.totalAmount)
}
}
Используя геттеры и сеттеры вы получаете полный контроль при управлении объектом:
- Можно настраивать дополнительную логику при считывании значения, например, ленивая инициализация.
- Можно настраивать дополнительную логику при задании значения, например, сброс значения другого поля.
- Можно управлять областью видимости полей, чтобы было удобнее работать с объектом извне.
public class SettingsProvider {
public void getSettings(SettingsCallback callback) {
// Get settings from cache
Settings cachedSettings = getCachedSettings();
// Needs to return cached if already exists.
boolean returnCached = cachedSettings != null;
// Needs to return remote only if cached does not exists.
boolean returnRemote = cachedSettings == null;
// Return cached settings if needs
if (returnCached) {
callback.onSuccess(cachedSettings);
}
// Always load actual settings from server
loadSettingsFromServer(new SettingsCallback() {
@Override
public void onSuccess(Settings settings) {
// Cache loaded settings
cacheSettings(settings);
// Return loaded settings if needs
if (returnRemote) {
callback.onSuccess(settings);
}
}
});
}
}
- Комментарии, повторяющие исходный код, не несут смысловой нагрузки и не дают информации о причине действий.
- Комментарии к каждой строке делают код разрозненным, из-за чего его сложнее читать.
/**
* Provides the access to the application settings.
* Can be used in the other code in order to abstract
* from the source of data (API, Firebase, Cache, etc.)
*/
public class SettingsProvider {
public void getSettings(SettingsCallback callback) {
// At the request of the customer needs to
// use the existing settings
// if they have been downloaded at least once
// but refresh the actual ones every time.
Settings cachedSettings = getCachedSettings();
boolean returnCached = cachedSettings != null;
boolean returnRemote = cachedSettings == null;
if (returnCached) {
callback.onSuccess(cachedSettings);
}
loadSettingsFromServer(new SettingsCallback() {
@Override
public void onSuccess(Settings settings) {
cacheSettings(settings);
if (returnRemote) {
callback.onSuccess(settings);
}
}
});
}
}
public class QueueProcessor {
private static final int WAIT_TIMEOUT = 1;
private Job currentJob;
private void runNextJob() {
Job job = getNextJob();
if (job != null) {
runJob(job);
}
}
private void wait() {
TimeUnit.SECONDS.sleep(WAIT_TIMEOUT);
}
private void clearCurrentJob() {
this.currentJob = null;
}
private void setCurrentJob(Job job) {
this.currentJob = job;
}
public void run() {
while (true) {
runNextJob();
wait();
}
}
private void runJob(Job job) {
setCurrentJob(job);
job.run();
clearCurrentJob();
}
private Job getNextJob() {
// Some logic to fetch the next job.
}
}
- Для чтения кода приходится несколько раз пролистывать файл вверх-вниз, что создает дополнительную нагрузку для разработчика.
public class QueueProcessor {
private static final int WAIT_TIMEOUT = 1;
private Job currentJob;
public void run() {
while (true) {
runNextJob();
wait();
}
}
private void runNextJob() {
Job job = getNextJob();
if (job != null) {
runJob(job);
}
}
private void wait() {
TimeUnit.SECONDS.sleep(WAIT_TIMEOUT);
}
private Job getNextJob() {
// Some logic to fetch the next job.
}
private void runJob(Job job) {
setCurrentJob(job);
job.run();
clearCurrentJob();
}
private void setCurrentJob(Job job) {
this.currentJob = job;
}
private void clearCurrentJob() {
this.currentJob = null;
}
}
- Если код дублируется, то при изменении алгоритма придется менять код в нескольких местах.
- Есть разные способы избавиться от дублирования кода. У каждого из них есть свои плюсы и минусы.
- Похожий код не всегда означает дублирование кода.
class UsersListScreen {
fun showClient(client: User, clientView: ClientView) {
clientView.firstName.text = client.firstName
clientView.lastName.text = client.lastName
val description =
"Email: " + client.email + "\n" +
"Address: " + client.address + "\n" +
"Registered at: " + client.registeredAt
clientView.description.text = description
}
fun showAdmin(admin: User, adminView: AdminView) {
adminView.fullName = admin.fullName
val description =
"Email: " + admin.email + "\n" +
"Address: " + admin.address + "\n" +
"Registered at: " + admin.registeredAt
adminView.description.text = description
}
}
class UsersListScreen {
fun showClient(client: User, clientView: ClientView) {
clientView.firstName.text = client.firstName
clientView.lastName.text = client.lastName
clientView.description = getDescription(client)
}
fun showAdmin(admin: User, adminView: AdminView) {
adminView.fullName = admin.fullName
clientView.description = getDescription(admin)
}
private fun getDescription(user: User): String {
return "Email: " + admin.email + "\n" +
"Address: " + admin.address + "\n" +
"Registered at: " + admin.registeredAt
}
}
Если код продублирован в одном классе, можно вынести его в функции этого же класса. Однако, не получится переиспользовать код в других классах. Пользуйтесь таким способом только в том случае, если нет предпосылок для его использования в других классах.
class ProfileScreen : BaseUserScreen {
fun showProfile(personalInfo: PersonalInfo) {
val parts = personalInfo.fullName.split(" ")
val firstName = parts[0]
val lastName = parts[1]
this.firstNameTextView.text = firstName
this.lastNameTextView.text = lastName
}
}
class UsersListScreen : BaseUserScreen {
fun showUser(user: User, userView: UserView) {
var parts = user.personalInfo.fullName.split(" ")
val firstName = parts[0]
val lastName = parts[1]
userView.firstName.text = firstName
userView.lastName.text = lastName
}
}
class ProfileScreen : BaseUserScreen {
fun showProfile(personalInfo: PersonalInfo) {
this.firstNameTextView.text = getFirstName(personalInfo)
this.lastNameTextView.text = getLastName(personalInfo)
}
}
class UsersListScreen : BaseUserScreen {
fun showUser(user: User, userView: UserView) {
val personalInfo = user.personalInfo
userView.firstName.text = getFirstName(personalInfo)
userView.lastName.text = getLastName(personalInfo)
}
}
open class BaseUserScreen {
protected fun getFirstName(personalInfo: PersonalInfo): String {
return getFullNameParts(personalInfo)[0]
}
protected fun getLastName(personalInfo: PersonalInfo): String {
return getFullNameParts(personalInfo)[1]
}
protected fun getFullNameParts(personalInfo: PersonalInfo): List<String> {
return personalInfo.fullName.split(" ")
}
}
Можно вынести дублирующийся код в базовый класс. Но получится переиспользовать код только в рамках одной иерархии.
class ProfileScreen : BaseObjectScreen {
fun showProfile(personalInfo: PersonalInfo) {
val parts = personalInfo.fullName.split(" ")
val firstName = parts[0]
val lastName = parts[1]
this.firstNameTextView.text = firstName
this.lastNameTextView.text = lastName
}
}
class UsersListScreen : BaseListScreen {
fun showUser(user: User, userView: UserView) {
var parts = user.personalInfo.fullName.split(" ")
val firstName = parts[0]
val lastName = parts[1]
userView.firstName.text = firstName
userView.lastName.text = lastName
}
}
class ProfileScreen : BaseObjectScreen {
fun showProfile(personalInfo: PersonalInfo) {
this.firstNameTextView.text = personalInfo.getFirstName()
this.lastNameTextView.text = personamInfo.getLastName()
}
}
class UsersListScreen : BaseListScreen {
fun showUser(user: User, userView: UserView) {
userView.firstName.text = user.personalInfo.getFirstName()
userView.lastName.text = user.personalInfo.getLastName()
}
}
class PersonalInfo {
// Existing fields, getters and setters
fun getFirstName(): String {
return this.fullNameParts()[0]
}
fun getLastname(): String {
return this.fullNameParts()[1]
}
private fun getFullNameParts(): List<String> {
return this.fullName.split(" ")
}
}
- Если код продублирован в разных классах одной иерархии, можно вынести его в другой класс, который используется в дублирующемся коде.
class PersonalInfoLogic(val persinalInfo: PersonalInfo) {
fun getFirstName(): String {
return this.fullNameParts()[0]
}
fun getLastName(): String {
return this.fullNameParts()[1]
}
private fun getFullNameParts(): List<String> {
return this.personalInfo.fullName.split(" ")
}
}
val personalInfo = getPersonalInfo()
val personalInfoLogic = PersonalInfoLogic(personalInfo)
personalInfoLogic.getFirstName()
personalInfoLogic.getLastName()
- Избавляйтесь от дублирующегося кода.
- Уделите внимание месту, куда будет вынесен одинаковый код (отсортировано по возможности переиспользования от худшего к лучшему):
- В тот же класс.
- В базовый класс иерархии.
- В существующий класс, который используется в дублирующемся коде.
- В новый специальный класс.
Если код выглядит одинаково, то это не обязательно дублирование.
class Model(val id: Integer, val name: String) {}
class Brand(val id: Integer, val name: String) {}
Здесь дублирование, это случайность, в рамках которой набор полей и их названий повторяются между двумя несвязанными классами.
- Используйте единый стиль именования для одинаковых вещей.
- Не делайте много преждевременной оптимизации.
- Удаляйте неиспользуемый и закомментированный код.
- Вместо покрытия кода комментариями пытайтесь отразить свои намерения в коде с помощью продуманного именования, введения пояснительных переменных и функций.
- Придерживайтесь стиля написания кода вашего языка/фреймворка.
- Используйте части речи по назначению
- Используйте глаголы как основу для названий функций.
- Используйте существительные для классов и переменных.
- Для Boolean используйте прилагательные и причастия.