public void processOrder(Order order) {
if (order != null) {
if (order.isComplete()) {
if (order.isPaid()) {
// Process the order
} else {
// Handle payment processing
}
} else {
// Handle incomplete order
}
}
}
invert-if해서 단순한 경우(order == null )부터 처리
들여쓰기(indentation)가 줄어들어 인지적 복잡도가 줄어듦
테스트를 추가할 때 Degenerate Test를 먼저 추가하면 함수의 앞부분에 guard clause가 먼저 추가됨
함수는 주요한 로직이 나중에 구현되고, 조건문으로 인한 들여쓰기도 적어지게 되어 복잡도가 감소함
Split Phase
split-phase-conveyor
d82a11f (HEAD -> split-phase-conveyor) make it testable
594e508 rename data structure
364b6de extract 1st step and introduce data structure
84517c6 extract 2nd step
d674afc extract variable
aff20fb introduct parameter
54e8735 add ConveyorService
sproutMethod_stepDownRule
2fc19e9 (HEAD -> sproutMethod_stepDownRule) call sprouted method - make it pass
d50d35e add failing test
553c983 subclass and override
73be13c rf - prepare subclass and override method
ec0c3de rf - change test to use OrderController
e05cdd5 rf - move method
df7ac81 rf - prepare move method
b4e4c08 rf - extract isEventProduct method
6abbaea add failing case
f72dae1 rf - move unnecessary steps in stream
c9a0f1d apply "subclass and override method"
fafe435 implement step down rule in test
5aa897e add step down rule
70fd197 (tag: s_sprout_subclass_override) prepare
dry-excel-download
878f927 (HEAD -> dry-excel-download) add split-phase-dry.pdf
8e1c199 apply generic
9a1ae83 extract superclass
e0f3478 re-order method - for reading order
3694724 extract variable part for template method
02e7858 extract field
8061714 extract ExcelFormatter
8f8411b rename format method
7453f0b remove static
5e765ad re-order methods for reading order and increasing cohesion
e6708b4 (tag: s_excel_download) add required classes
6fcd1dc (HEAD -> split_phase_from_refactroing_2nd) writerside
717f888 extract first-phase to return intermediate DS
cdddf6a add discount to PriceData
36763da add quantity to PriceData
2f10adf safe delete basePrice
58e89d9 add basePrice to PriceData
e4db4d5 introduce intermediate data structure that will communicate between the two phases
d887fe5 extract method - applyShipping
baa3837 (tag: s_split_phase_from_refectoring2nd.ed) add required fils for split_phase from refactoring 2nd ed.
split-phase-commandLineRunner
8b24d01 (HEAD -> split-phase-commandLineRunner) extract delegate - split by levels of abstraction
0d06e83 extract parameter
4f41ca0 invert if, remove redundant else
c5ad49a extract method readOrders
f9827c0 inline method, replace anyMatch with contains
b3ed47c move instance method to value object
db797b1 extract method count(2nd phase)
0d0691e extract method parse to introduce value object
2e1d58e extract variable and slide statement
77fc112 extract methods - countReadyOnly
c50d701 add test
0812e60 remove static
54d00fd make it return value for testability
864f3c3 extract method
38a9a14 add CommandLineRunner
split_phase_victor
f226b3d (HEAD -> split_phase_victor) make arch unit test works
683da9f add arch unit
0ee3f75 add warning threshold
03a7297 add lombok.config
1472766 add new mock test without excel stuffs
c57ec95 extract parameter to reduce dependency scope
2ee0b53 extract delegate for levels of abtraction
ffcdecd split BL with excel logic
9972deb reorder method
123b414 split phase
c05de52 reorder method
b83958e extract method(BL)
a93f4f4 (tag: s_split_phase_victor) add required files for split phase
createProductOption을 Application Service(Use Case)로 이동
split-by-abstraction-layer-inventory-transfer
30ecfdc (HEAD -> main, split-by-abstraction-layer-inventory-transfer) extract delegate - InventoryTransferFactory [Myeongseok Baek]
306271b make it work [Myeongseok Baek]
0be65b0 extract delegate - CreateInventoryTransferValidator [Myeongseok Baek]
455a590 before split phase - split by abstraction layer [Myeongseok Baek]
unrelated_complexity
73b271a (HEAD -> unrelated_complexity) writerside
6e59a43 extract prod & make it work
fd0132f extract test & make it works
e714df7 remove dup cause - Unnecessary stubbings detected.
376fbea add more test - DRY !!
edf3319 (tag: s_unrelated_complexity) add required files for "split unrelated complexity"
Valuable Value Object
introduce-parameter-object
d14f3f3 (HEAD -> introduce-parameter-object) move instance method
7817acf inline method to move instance method
fa816dc extract method isStock to use stockChecker only
4bc5327 introduce parameter object - StockChecker
88f88ee extract parameter to reduce dependency to request(DTO)
66d35e7 extract method - check. 빵속빵 구조로
a6f1e1c add StockCheckPort
Parameter Object가 적합 한 경우
일련의 상태에 대해서 동작하는 기능들이 있을 때
일련의 의존성 객체들을 사용하는 기능이 있을 때
valuable-parameter-object 38abf79 move instance method to value object
38abf79 (HEAD -> valuable-parameter-object) move instance method to value object
5007a31 extract methods that uses value object
6a8194f safe delete unused variable
045644d use value object instead of local variable
37b90fe inline this
d4d4591 extract variable shipping
4396534 introduce parameter object
6930e92 add additional parameter
e14b4be add class for extract value object
replace stream api chain with loop, inline methods 등은 one pile임
a433b9a double-stream-collection add required files
1837c9f Replace Stream API chain with loop
998789b remove mutation(accumulative variable)
a521184 extract method and rename it
a1a725d one pile. inline methods
3772c96 introduce parameter object
92beb6f move method to parameter object
597c68e extract variable and slide statement
24aff90 Collapse loop with stream 'collect'
754c1ba extract method toward composed method
3933a1f extract factory method, make it static, move to ...
0dab02a extract methods
b821ebc (HEAD -> double-stream-first-class-collection) Collapse loop with stream 'collect'
entity-fields-to-value-object
403c424 (HEAD -> entity-fields-to-value-object, main) extract delegate
06ab3c0 inline getter to extract delegate
2b365b1 add Order
095d08e Merge branch 'split-phase-commandLineRunner'
From Primitive Obsession to Domain Modelling
Primitive obsession is a type of code smell that developers can’t identify intuitively.
It occurs when a primitive value controls the logic in a class and represents complex concepts or behaviors. In simple words, when a code relies too much on primitive values.
Using primitives for everything is certainly a bad practice. This leads to poor readability, validation, and abstraction.
Solutions
Replace the data value with the object if the primitive fields logically belong together.
‘Introduce a parameter object’ to represent the data and clean up the code base.
‘Preserve the whole object’ when its state is needed together. Avoid extracting small parts of objects to pass around.
9b9a4dc (HEAD -> main, primitive-obsession) primitive obsession - inline unnecessary test test createUser
a959a28 primitive obsession - move call to validateUserName from UserNameTest(external) to UserName
ab4fb7a primitive obsession - move 2 static methods to Value Object(UserName)
a42859c primitive obsession - extract method validateUserName in order to move
f06b7ee primitive obsession - make methods static to move
3b50072 primitive obsession - change construction logic
e061317 primitive obsession - extract delegate with getter/setter
697f857 primitive obsession - add getter/setter, remove final for extract delegate
adb9d54 primitive obsession - add normal case
75e1965 primitive obsession - make it works
f22aa28 primitive obsession - add failing test
User entity → User Entity, UserName Value Object = Aggregate
lift-up-conditional
140f159 (HEAD -> lift-up-conditional) lift up condition - for isSulfuras
7ff06b5 lift up condition - for Backstage
2d4ae5d lift up condition 5 - deal with IDE warnings
5afd963 lift up condition 4 - inline
7202d98 lift up condition 3 - surround with if-else
13b69b6 lift up condition 2 - extract method
ba8c25f lift up condition 1 - instroduce conditional variable
46366fd move instance method
3b13ee1 extract method - updateItem
e4431ba deal IDE warnings
b505eac (tag: s_liftup) add CombinationApprovals.verifyAllCombinations
repeated-switch
04911a8 parrot - add files
f531eb0 parrot - Replace constructor with factory method
9e7ae25 parrot - extract variable parrot
8e7433b parrot - Split into declaration and assignment
7247d54 parrot - apply switch in factory method
1badd1d parrot - add subclasses
1559e4f parrot - use known constant instead of passed argument(type)
ed0788a parrot - Safe delete 'type'
5ffe08d parrot - repeat remove type in constructor in other 2 subclasses
d149d5e parrot - Push Members Down...
124823f parrot - make it work
0dade53 parrot - use constant in switch
4f97715 parrot - Replace with old style 'switch' statement
aabd05d parrot - Remove unreachable branches
9ed89ac parrot - inline unnecessary variable
53e8619 parrot - replace type with constant and remove unreachable branches
bdd00d1 parrot - push members down(getSpeed)
486a5c2 parrot - make it work
8cd4322 parrot - replace type with constant and remove unreachable branches
5818024 parrot - find usage and push members down
1feaefe parrot - make it work. remove unnecessary field, parameter
0fa0889 parrot - find usage, push members down, make it work
2fa8836 (HEAD -> main, parrot) parrot - find usage, push members down, make it work for isNailed
b723c5a polymorhic-enum: add required files
a174dcb polymorhic-enum: extract method to move
6af490d polymorhic-enum: extract parameter to prepare move method
987db3a polymorhic-enum: move method
cd45eb1 polymorhic-enum: push members down manually(because enum is final)
extract common part
8451c17 extract-common-parts - add required files
33df5ff extract-common-parts - replate temp with query before refactoring
d3f85da extract-common-parts - extract method for remove dup.(with keep original signature)
cf65bf4 (HEAD -> main, extract-common-parts) extract-common-parts - Extract common part removing branch
stream api를 loop를 활용해서 개선하기
최초 메소드
6f03714 refactor-stream add required files
public class ConveyorRegistry {
/**
* @param itemOptions 검수 단계에서 측정된 실제 무게를 가져오기 위한 아이템 정보
* @param shippingItems 배송할 아이템 정보
* @return 전체 무게를 반환.
*/
public Long calculateTotalWeight(final List<ItemOption> itemOptions, final List<ShippingItem> shippingItems) {
return shippingItems.stream()
.mapToLong(shippingItem -> {
final Long itemId = shippingItem.getItemId();
final Long qty = shippingItem.getQty();
return itemOptions.stream()
.filter(itemOption -> Objects.equals(itemOption.getId(), itemId))
.findFirst()
.map(ItemOption::getWeight)
.orElse(0L) * qty;
})
.sum();
}
}
Add Test using Copilot
d157346 refactor-stream add test
class ConveyorRegistryTest {
@Test
public void testCalculateTotalWeight() {
ConveyorRegistry conveyorRegistry = new ConveyorRegistry();
List<ItemOption> itemOptions = Arrays.asList(
new ItemOption() {
public Long getId() {
return 1L;
}
public Long getWeight() {
return 2L;
}
},
new ItemOption() {
public Long getId() {
return 2L;
}
public Long getWeight() {
return 3L;
}
}
);
List<ShippingItem> shippingItems = Arrays.asList(
new ShippingItem() {
public Long getItemId() {
return 1L;
}
public Long getQty() {
return 2L;
}
},
new ShippingItem() {
public Long getItemId() {
return 2L;
}
public Long getQty() {
return 3L;
}
}
);
Long expectedTotalWeight = 2L * 2L + 3L * 3L;
Long actualTotalWeight = conveyorRegistry.calculateTotalWeight(itemOptions, shippingItems);
assertEquals(expectedTotalWeight, actualTotalWeight);
}
}