Мы тратим больше времени на чтение кода, чем на написание. Так что, если код будет более читабельным, то это, очевидно, увеличит производительность разработчика.
Многие люди связывают удобочитаемость кода с соглашениями о кодировании, такими как следование стандартным соглашениям об именах, закрытие файла, ресурсы БД и т. Д. Когда речь заходит о проверках кода, большинство людей сосредотачиваются только на этих тривиальных вещах, таких как проверка нарушений соглашения об именах, правильное освобождение ресурсов. в конечном итоге блок или нет.
Нужны ли нам «старшие ресурсы» в команде (я ненавижу называть человека ресурсом), чтобы делать такие вещи? Инструменты типа Findbugs, PMD, Checkstyle, Sonar могут сделать это для вас. Я согласен, что соблюдение стандартного соглашения об именах всеми членами команды — это хорошо. Но это не повышает читаемость кода.
Давайте возьмем простой пример. Я хотел бы реализовать сценарий перевода средств, и следующие правила для реализации:
- Исходные и целевые учетные записи должны быть действительными учетными записями.
- Проверьте, достаточно ли у исходного аккаунта
- Проверьте, есть ли в исходной учетной записи резерв овердрафта, и проверьте, превышает ли эта транзакция лимит овердрафта.
- Проверьте дубликат транзакции с последней транзакцией. Если исходная, целевая учетная запись и сумма совпадают с последней транзакцией, считайте ее дубликатом.
- Если все хорошо, тогда переведите сумму на целевой счет
Предположим, у нас есть следующая реализация для приведенного выше варианта использования:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
public class UglyMoneyTransferService { public void transferFunds(Account source, Account target, BigDecimal amount, boolean allowDuplicateTxn) throws IllegalArgumentException, RuntimeException { Connection conn = null ; try { conn = DBUtils.getConnection(); PreparedStatement pstmt = conn.prepareStatement( "Select * from accounts where acno = ?" ); pstmt.setString( 1 , source.getAcno()); ResultSet rs = pstmt.executeQuery(); Account sourceAccount = null ; if (rs.next()) { sourceAccount = new Account(); populate account properties from ResultSet } if (sourceAccount == null ){ throw new IllegalArgumentException( "Invalid Source ACNO" ); } Account targetAccount = null ; pstmt.setString( 1 , target.getAcno()); rs = pstmt.executeQuery(); if (rs.next()) { targetAccount = new Account(); populate account properties from ResultSet } if (targetAccount == null ){ throw new IllegalArgumentException( "Invalid Target ACNO" ); } if (!sourceAccount.isOverdraftAllowed()) { if ((sourceAccount.getBalance() - amount) < 0 ) { throw new RuntimeException( "Insufficient Balance" ); } } else { if (((sourceAccount.getBalance()+sourceAccount.getOverdraftLimit()) - amount) < 0 ) { throw new RuntimeException( "Insufficient Balance, Exceeding Overdraft Limit" ); } } AccountTransaction lastTxn = .. ; JDBC code to obtain last transaction of sourceAccount if (lastTxn != null ) { if (lastTxn.getTargetAcno().equals(targetAccount.getAcno()) && lastTxn.getAmount() == amount && !allowDuplicateTxn) { throw new RuntimeException( "Duplicate transaction exception" );ask for confirmation and proceed } } sourceAccount.debit(amount); targetAccount.credit(amount); TransactionService.saveTransaction(source, target, amount); } catch (Exception e){ logger.error( "" ,e); } finally { try { conn.close(); } catch (Exception e){ Not everything is in your control..sometimes we have to believe in GODJamesGosling and proceed } } } } |
Вышеприведенный код читабелен .. верно ?? … потому что:
- Мы следовали соглашениям об именах, таких как имена переменных в верблюжьей оболочке
- У нас есть все открытые скобки ({) в строке определения метода
- Мы закрыли соединение с БД в блоке finally
- мы зарегистрировали исключение вместо использования System.err.println ()
- и самое главное, он работает, как ожидалось.
Так читаемый и чистый код? На мой взгляд, абсолютно нет. В приведенном выше коде много проблем с точки зрения читабельности.
- Смешивание кода взаимодействия БД с бизнес-логикой
- Создание исключений IllegalArgumentException, RuntimeException и т. Д. Из бизнес-методов вместо бизнес-исключений
- Самое главное, код смешан с различными уровнями абстракций.
Позвольте мне объяснить, что я имею в виду под различными уровнями абстракций.
Во-первых, с точки зрения бизнеса перевод средств означает проверку исходных / целевых счетов, проверку достаточного остатка, проверку лимита овердрафта, проверку дублирующих транзакций и осуществление перевода средств.
С технической точки зрения существуют различные задачи, такие как получение сведений об учетной записи из БД, выполнение всех проверок, связанных с бизнесом, создание исключений в случае каких-либо нарушений, надлежащее закрытие ресурсов и т. Д.
Но в приведенном выше коде все перемешано.
Читая код, вы начинаете смотреть на код JDBC, и ваш мозг работает в техническом режиме, а после получения объекта Account из ResultSet вы проверяете наличие нуля и выдает исключение, если оно равно нулю, что является бизнес-требованием. Поэтому вам сразу нужно переключиться на бизнес-режим и подумать: «Хорошо, если учетная запись недействительна, мы хотим немедленно прервать операцию».
Несмотря на то, что вам удалось переключиться между Техническим и Бизнес-режимами, как насчет улучшения одной подзадачи, такой как «Перевод средств считается дубликатом, только если он совпадает с последней транзакцией, которая произошла только через час». Чтобы сделать это усовершенствование, вы должны пройти весь метод, потому что вы не модульные свои подзадачи и нет разделения интересов.
Давайте перепишем описанный выше метод следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
class FundTransferTxn { private Account sourceAccount; private Account targetAccount; private BigDecimal amount; private boolean allowDuplicateTxn; setters & getters } public class CleanMoneyTransferService { public void transferFunds(FundTransferTxn txn) { Account sourceAccount = validateAndGetAccount(txn.getSourceAccount().getAcno()); Account targetAccount = validateAndGetAccount(txn.getTargetAccount().getAcno()); checkForOverdraft(sourceAccount, txn.getAmount()); checkForDuplicateTransaction(txn); makeTransfer(sourceAccount, targetAccount, txn.getAmount()); } private Account validateAndGetAccount(String acno){ Account account = AccountDAO.getAccount(acno); if (account == null ){ throw new InvalidAccountException( "Invalid ACNO :" +acno); } } private void checkForOverdraft(Account account, BigDecimal amount){ if (!account.isOverdraftAllowed()){ if ((account.getBalance() - amount) < 0 ) { throw new InsufficientBalanceException( "Insufficient Balance" ); } } else { if (((account.getBalance()+account.getOverdraftLimit()) - amount) < 0 ){ throw new ExceedingOverdraftLimitException( "Insufficient Balance, Exceeding Overdraft Limit" ); } } } private void checkForDuplicateTransaction(FundTransferTxn txn){ AccountTransaction lastTxn = TransactionDAO.getLastTransaction(txn.getSourceAccount().getAcno()); if (lastTxn != null ) { if (lastTxn.getTargetAcno().equals(txn.getTargetAccount().getAcno()) && lastTxn.getAmount() == txn.getAmount() && !txn.isAllowDuplicateTxn()) { throw new DuplicateTransactionException( "Duplicate transaction exception" ); } } } private void makeTransfer(Account source, Account target, BigDecimal amount){ sourceAccount.debit(amount); targetAccount.credit(amount); TransactionService.saveTransaction(source, target, amount); } } |
Вышеупомянутый улучшенный метод делает именно то, что делает первоначальная версия, но теперь он выглядит намного лучше, чем предыдущая версия.
- Мы разделили всю задачу на подзадачи и реализовали каждую подзадачу отдельным методом.
- Мы делегировали взаимодействие с БД в DAO
- Мы бросаем бизнес-специфические исключения вместо исключений языка Java
- В целом мы разделили уровни абстракций.
На первом уровне у нас самый высокий уровень абстракции в методе TransferFunds (FundTransferTxn txn). Рассматривая этот метод, мы можем понять, что мы делаем в рамках операции «Перевод средств», не сильно заботясь о реализации или технических деталях.
На втором уровне у нас есть методы реализации бизнес-логики checkForOverdraft, checkForDuplicateTransaction и т. Д., Которые выполняют бизнес-логику, опять же, не сильно заботясь о технических деталях.
На самом низком уровне у нас есть технические детали реализации в AccountDAO и TransactionDAO, которые содержат логику взаимодействия с БД.
Таким образом, читатель (будущий разработчик / сопровождающий) вашего кода может легко понять, что вы делаете на высоком уровне, и найти метод, который ему интересен.
Как я уже говорил ранее, если мы должны внести изменение, чтобы рассматривать транзакцию как дублирующую транзакцию, только если она произошла в течение часа, мы можем легко понять, что checkForDuplicateTransaction () — это та, которую мы должны рассмотреть и внести изменения.
Удачного кодирования!