변한다!
대부분의 소프트웨어는 현실의 문제를 해결하기 위한 솔루션으로 만들어진다.
여기서의 현실은? 매번 변하게 된다!
당연히 현실의 문제가 바뀌니까, 현실의 문제를 해결하는 소프트웨어도 바뀌어야 한다!
그래서! 소프트웨어는 굉장히 자주, 그리고 지속적으로 바뀌게 된다.
당연하게 바로 개발자! (물론 나도... 힛)
개발자들은 소프트웨어를 이루고 있는 코드를 자주 바꿀 수 밖에 없다.
그럼, 소프트웨어가 자주 변화하는 환경에서 개발자들이 생각해야 할 점은 무엇일까?
현재 소프트웨어 차지 비율이 점점 늘고 있고, 소프트웨어에 의존성 또한 커지고 있다.
그만큼 소프트웨어에게 주어지는 책임감도 커져있는것이 사실이다.
이와 같은 환경을 전제로 깔 경우,
소프트웨어는 계속 바뀌어야 하고, 개발자들은 안전하게 잘 바꿔나가야 한다.
위에서 언급한 책임감을 가지고 개발하기 위해서는 이런 사항들을 충분히 고려해야 한다!
수 많은 소프트웨어 개발, 프로그래밍 패턴과 같은 것들이 대부분 변경 용이성에 초점이 맞춰져 있다.
(아주 다양한 프로그래밍 패턴들이 존재하고, 아직 나는 이 부분에 대한 지식이 없으므로 조만간 공부해야봐야 겠다!!! )
아무튼, 이 다양한 프로그래밍 패턴은 굉장히 다양하고 복잡하지만, 이의 근본은
이와 같은 사항들에 초점이 많이 맞춰져 있다.
function createEl(type, props){
switch(type){
case 'h1':
return [document.createElement('h1')]
.map(el => {
Object
.entries({...props, 'data-id':'title'})
.forEach(([name, value]) => el.setAttribute(name, value))
return el;
})[0];
case 'div':
return [document.createElement('div')]
.map(el => {
Object
.entries({...props, 'data-id':'layout'})
.forEach(([name, value]) => el.setAttribute(name, value))
return el;
})[0];
}
}
코드 설명
1)type
에 들어오는 문자열에 따라createElement
를 이용하여DOM API
로DOM node
를 하나 만들고,Atrribute
에data-id
속성을 추가하여 내용을 넣는다.2) 또 다른 인자
props
객체를setAttribute
를 통해 주입 하고, 만들어진element
를return
한다.위 함수의 경우
switch case
가 여러가지 늘어날 수 있다. (확장 가능성 있는 함수)
소프트웨어는 변한다고 했으니, 그런 상황을 가정해보자!
어떤 변화가 생길 수 있을까?
createEl
의 case
추가case
구문 안의 로직 변경등등 이 있을 수 있다.
여기서,
h1
태그 생성 로직을 수정한다고 생각해본다면, div
태그 생성 로직 상관이 없는 변경이 된다.
즉, h1
생성 로직 범위만 변경 되었지만 안전하게 아무 이상없이 동작된다는 것을 테스트 해야 하는 범위는 createEl
함수 전체가 된다. 현재는 바뀐 부분만 테스트 할 수 없는 구조로 되어있기 때문!
변경 이후 안정성을 어느 정도 확보해 줄 것인가? 라는 측면에서 본다면 위의 함수는 변경에 용이하지 않은 구조라는 것을 알 수 있다.
이런 부분을 고려하여 첫번째 리펙토링을 해보자!
case
들은 개별 로직이기 때문에, 따로 분리해볼 수 있다.
function createH1(props) {
return [document.createElement("h1")].map((el) => {
Object.entries({ ...props, "data-id": "title" }).forEach(([name, value]) =>
el.setAttribute(name, value)
);
return el;
})[0];
}
function createDiv(props) {
return [document.createElement("h1")].map((el) => {
Object.entries({ ...props, "data-id": "title" }).forEach(([name, value]) =>
el.setAttribute(name, value)
);
return el;
})[0];
}
function createEl(type, props) {
switch (type) {
case "h1":
return createH1(props);
case "div":
return createDiv(props);
}
}
코드 설명
바깥 쪽으로createH1
함수와createDiv
함수를 분리
리펙토링된 함수를 살펴 보면,
h1
태그를 생성하는 로직에 변경사항이 생기면 createH1
만 온전히 테스트하면 된다.
리펙토링 전 코드( [A] )는 모든 코드가 createEl
함수 안에 다 들어가 있었기 때문에
createEl
함수 전체를 테스트 했어야 했다. (실제 변경 범위와 테스트 범위가 달라지는 문제가 발생)
이런 측면에서 본다면, [A]코드보다는 [B]코드가 개선 된 것을 알 수 있다.
하지만, 아직은 추가에 취약하다는 문제가 남아있다.
만약 태그를 추가하는 함수가 생긴다면, (createInput
, createH2
, createH3
....)
createEl
함수의 case
문이 추가 되어야 한다.
이런 경우는 createEl
함수의 다른 case
은 아무런 상관이 없음에도 불구하고 createEl
함수를 전체 테스트 해봐야한다는 문제가 생긴다.
결국 [A]처럼 변경 범위와 테스트 범위가 달라지는 문제가 다시 발생하게 된 것이다.
그럼 추가 했을 경우에도 createEl
함수의 영향 범위 안에 들어가지 않도록 switch
구문을 없애보면 될까? 다시 리펙토링 해보자!
createEl
함수의switch
문을 보면,
문자열과 함수가 연결 되어있는 구조이고, type
은 어떤 함수를 호출 할 것인가에 대한 트리거 역할만 하는 값이다.
이를 이용해서 맵핑 정보를 만들어두면, 맵핑 정보만 전달하고, 그 정보에 따라 호출만 해주는 식으로 바꾸면 되겠다!
function createH1(props) {
return [document.createElement('h1')]
.map(element => {
Object
.entries({ ...props, 'data-id': 'subject' })
.forEach(([name, value]) => element.setAttribute(name, value))
return element;
})[0];
}
function createDiv(props) {
return [document.createElement('div')]
.map(element => {
Object
.entries({ ...props, 'data-id': 'layout' })
.forEach(([name, value]) => element.setAttribute(name, value))
return element;
})[0];
}
const creatorMap = {
h1: createH1,
div: createDiv,
};
function createEl(type, props) {
return creatorMap[type][props]
}
createEl
가 훨씬 안정적인 모양을 취하게 되었다.
태그 생성 함수가 추가 된다면 creatorMap
에 추가만 해주면 된다.
이렇게 추가 시에도 createEl
함수는 전혀 변경이 없게 된다.
변화 시에 그 변화에 대한 영향 범위를 특정 범위로 굉장히 축소할 수 있는 코드로 만들어 진다면, 훨씬 더 안전, 단단, 견고한 코드가 된다는 것을 알 수 있게 되었다~!
한번 더 코드를 확인해보면 아직 변경사항이 남아있다!
createEl
함수가 creatorMap
이라는 외부 변수에 의존하고 있다.
그래서, 이 외부 변수인 creatorMap
가 바뀌게 되었을 경우 createEl
함수가 동작이 잘 안될 가능성이 존재한다.
마지막으로 이 부분을 제거해보자!
function createH1(props) {
return [document.createElement('h1')]
.map(element => {
Object
.entries({ ...props, 'data-id': 'subject' })
.forEach(([name, value]) => element.setAttribute(name, value))
return element;
})[0];
}
function createDiv(props) {
return [document.createElement('div')]
.map(element => {
Object
.entries({ ...props, 'data-id': 'layout' })
.forEach(([name, value]) => element.setAttribute(name, value))
return element;
})[0];
}
const creatorMap = {
h1: createH1,
div: createDiv,
};
const coupler = map => (type, props) => map[type](props);
const createEl = coupler(creatorMap);
createEl
는 외부 변수인 creatorMap
이라고 하는 객체의 종속성을 끊어 냈고, coupler
함수를 이용해서 안전하게 동작할 수 있는 구조가 되었다!
바깥쪽에서는 createEl
인터페이스를 계속 유지하고 있으니 사용하는 방법은 [A], [B], [C], [D] 모두 같다!
✍ 코드의 내용은 간단할 수 있지만, 위와 같은 리렉토링을 해보면서 소프트웨어의 변화에 어떤식으로 안전하게 코드로 대응할 수 있을지를 한번 더 생각하고 고민해보는 계기가 되었다