序
本文主要研究一下软件开发的SLAP(Single Level of Abstraction Principle)原则
SLAP
SALP即Single Level of Abstraction Principle的缩写,即单一抽象层次原则。
在Robert C. Martin的<
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。函数中混杂不同抽象层级,往往让人迷惑。读者可能无法判断某个表达式是基础概念还是细节。更恶劣的是,就像破损的窗户,一旦细节与基础概念混杂,更多的细节就会在函数中纠结起来。
这与 Don't Make Me Think 有异曲同工之妙,遵循SLAP的代码通常阅读起来不会太费劲。
另外没有循序这个原则的通常是Leaky Abstraction
【聊聊软件开发的SLAP原则】要遵循这个原则通常有两个好用的手段便是抽取方法与抽取类。
实例1
public List
这段代码包含两个抽象层次,一个是循环将resultSet转为
可以进一步抽取转换ResultDto的逻辑到新的方法中List
,一个是转换ResultEntity到ResultDto
public List
这样重构之后,buildResult就很清晰
实例2
public MarkdownPost(Resource resource) {
try {
this.parsedResource = parse(resource);
this.metadata = https://www.it610.com/article/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 = https://www.it610.com/article/extractMetadata(parsedResource);
this.url = urlFor(resource);
} catch (IOException e) {
throw new RuntimeException(e);
}
}private String urlFor(Resource resource) {
return"/" + resource.getFilename().replace(EXTENSION, "");
}
实例3
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 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。
doc