sequence
This article mainly studies the SLAP (Single Level of Abstraction Principle) principle of software development
SLAP
SALP is the abbreviation of Single Level of Abstraction Principle, which is the single abstraction level principle.
In the function chapter of Robert C. Martin's book <<Clean Code>> it is mentioned:
To ensure that a function does only one thing, the statements in the function must all be at the same level of abstraction. The jumble of different levels of abstraction in functions can often be confusing. The reader may not be able to tell whether an expression is a basic concept or a detail. Worse yet, like a broken window, once the details get mixed up with the underlying concepts, more details get tangled up in the function.
This is similar to Don't Make Me Think , where SLAP-compliant code is usually not too strenuous to read.
In addition, there is usually Leaky Abstraction without this principle.
To follow this principle, there are usually two useful means to extract methods and extract classes.
Example 1
public 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;
}
This code contains two levels of abstraction, one is to convert resultSet to List<ResultDto>
in a loop, the other is to convert ResultEntity to ResultDto
The logic of converting ResultDto can be further extracted into a new method
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;
}
After this refactoring, the buildResult is very clear
Example 2
public 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);
}
}
The assembly logic of the url here is not at the same level as other methods, and the reconstruction is as follows
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, "");
}
Example 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
}
}
}
}
This code leaks the dao logic into the service, and the verification logic is also coupled with the core business logic, which seems a bit laborious. It is reconstructed according to the SLAP principle as follows
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);
}
}
After the refactoring, the logic of transferFunds is very clear. First, check the account, then check whether there is an excess, then check whether the transfer is repeated, and finally execute the core makeTransfer logic
summary
SLAP is similar to Don't Make Me Think , and code that follows SLAP is usually not too hard to read. In addition, there is usually Leaky Abstraction without this principle.
doc
- Clean Code - Single Level Of Abstraction
- Clean Code: Don't mix different levels of abstractions
- Single Level of Abstraction (SLA)
- The Single Level of Abstraction Principle
- SLAP Your Methods and Don't Make Me Think!
- Levels of Abstraction
- Maintain a Single Layer of Abstraction at a Time | Object-Oriented Design Principles w/ TypeScript
- Talking about SLAP: The Single Abstraction Level Principle
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。