关于arch:聊聊软件开发的REPCCPCRP原则

序本文次要钻研一下软件开发的REP、CCP、CRP准则 The Reuse/Release Equivalence Principle (REP)复用/公布等同准则The granular of reuse is the granular of release.软件复用的最小粒度应等同于其公布的最小粒度,须要有本人的公布版本号。The Common Closure Principle (CCP)独特闭包准则,即组件层面的繁多职责准则The classes in a component should be closed together against the same kinds of changes. A change that affects a component affects all the classes in that component and no other components.咱们应该将那些会同时批改,并且为雷同目标而批改的类放到同一个组件中,而将不会同时批改,并且不会为了雷同目标而批改的那些类放到不同的组件中。 对大部分应用程序来说,可维护性的重要性要远远高于可复用性。如果某程序中的代码必须要进行某些变更,那么这些变更最好都体现在同一个组件中,而不是散布于很多个组件中。因为如果这些变更都集中在同一个组件中,咱们就只须要重新部署该组件,其余组件则不须要被从新验证、重新部署了 The Common Reuse Principle (CRP)独特复用准则The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.不要强制一个组件的用户依赖他们不须要的货色。倡议咱们将常常独特复用的类和模块放在同一个组件中。小结REP和CCP准则是黏合性准则,它们会让组件变得更大,而CRP准则是排除性准则,它会尽量让组件变小。 ...

April 6, 2022 · 1 min · jiezi

关于arch:聊聊软件开发的SLAP原则

序本文次要钻研一下软件开发的SLAP(Single Level of Abstraction Principle)准则 SLAPSALP即Single Level of Abstraction Principle的缩写,即繁多抽象层次准则。在Robert C. Martin的<<Clean Code>>一书中的函数章节有提到: 要确保函数只做一件事,函数中的语句都要在同一形象层级上。函数中混淆不同形象层级,往往让人蛊惑。读者可能无奈判断某个表达式是根底概念还是细节。更顽劣的是,就像破损的窗户,一旦细节与根底概念混淆,更多的细节就会在函数中纠结起来。这与 Don't Make Me Think 有殊途同归之妙,遵循SLAP的代码通常浏览起来不会太吃力。 另外没有循序这个准则的通常是Leaky Abstraction 要遵循这个准则通常有两个好用的伎俩便是抽取办法与抽取类。 实例1public List<ResultDto> buildResult(Set<ResultEntity> resultSet) { List<ResultDto> result = new ArrayList<>(); for (ResultEntity entity : resultSet) { ResultDto dto = new ResultDto(); dto.setShoeSize(entity.getShoeSize()); dto.setNumberOfEarthWorms(entity.getNumberOfEarthWorms()); dto.setAge(computeAge(entity.getBirthday())); result.add(dto); } return result;}这段代码蕴含两个抽象层次,一个是循环将resultSet转为List<ResultDto>,一个是转换ResultEntity到ResultDto能够进一步抽取转换ResultDto的逻辑到新的办法中 public List<ResultDto> buildResult(Set<ResultEntity> resultSet) { List<ResultDto> result = new ArrayList<>(); for (ResultEntity entity : resultSet) { result.add(toDto(entity)); } return result;} private ResultDto toDto(ResultEntity entity) { ResultDto dto = new ResultDto(); dto.setShoeSize(entity.getShoeSize()); dto.setNumberOfEarthWorms(entity.getNumberOfEarthWorms()); dto.setAge(computeAge(entity.getBirthday())); return dto;}这样重构之后,buildResult就很清晰实例2public MarkdownPost(Resource resource) { try { this.parsedResource = parse(resource); this.metadata = extractMetadata(parsedResource); this.url = "/" + resource.getFilename().replace(EXTENSION, ""); } catch (IOException e) { throw new RuntimeException(e); } }这里的url的拼装逻辑与其余几个办法不在一个档次,重构如下public MarkdownPost(Resource resource) { try { this.parsedResource = parse(resource); this.metadata = extractMetadata(parsedResource); this.url = urlFor(resource); } catch (IOException e) { throw new RuntimeException(e); }}private String urlFor(Resource resource) { return "/" + resource.getFilename().replace(EXTENSION, "");}实例3public 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 GOD/JamesGosling and proceed } }} }这段代码把dao的逻辑泄露到了service中,另外校验的逻辑也与外围业务逻辑耦合在一起,看起来有点吃力,按SLAP准则重构如下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); } return account; } 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); } }重构之后transferFunds的逻辑就很清晰,先是校验账户,再校验是否超额,再校验是否反复转账,最初执行外围的makeTransfer逻辑小结SLAP与 Don't Make Me Think 有殊途同归之妙,遵循SLAP的代码通常浏览起来不会太吃力。另外没有循序这个准则的通常是Leaky Abstraction。 ...

April 5, 2022 · 3 min · jiezi