2024년 3월 셋째주
제어의 역전 (Inversion Of Control)
- 외부의 API에서 실행 되어야 하는 작업을 사용자(프로그래머)에게 넘기는 것
filter라는 메서드를 구현한다고 했을 때, 아래와 같이 구현을 해본다고 하자.
function filter(array) {
let newArray = []
for (let index = 0; index < array.length; index++) {
const element = array[index]
if (element !== null && element !== undefined) {
newArray[newArray.length] = element
}
}
return newArray
}
// 사용처
filter([0, 1, undefined, 2, null, 3, 'four', ''])
만약 위의 코드에서 0만 필터링 해주는 기능을 추가한다고 하면 어떨까? filter 메서드에 매개변수를 추가하고, 내부의 분기문을 하나 더 추가하고 하며 점점 더 filter 함수가 비대해진다. 이렇게 비대해지는 함수는 유지보수에 취약하다.
🧵 제어의 역전 사용해보기
function filter(array, filterFn) {
let newArray = []
for (let index = 0; index < array.length; index++) {
const element = array[index]
if (filterFn(element)) {
newArray[newArray.length] = element
}
}
return newArray
}
filter(
[0, 1, undefined, 2, null, 3, 'four', ''],
el => el !== null && el !== undefined
)
위와 같이 구현하게 된다면 filter 함수는 여러 요구사항에 맞춰서 대응될 필요가 없다.
즉 필터링하는 책임을 filter메서드에서 제어하는게 아니라, 사용처에 위임
한 것이다. 그래서 제어의 역전이다.
- 리액트에서 컴파운드 컴포넌트 역시 제어의 역전의 예시이다.
- 컴파운드 컴포넌트 패턴 역시 하나의 컴포넌트에서 모든 책임을 제어하는게 아니라, 외부에 책임을 위임시켜버림으로써 많은 유즈케이스에 대응 할 수 있다.
그렇다고 무조건 제어의 역전이 정답일까? 언제나 그렇듯 과한 추상화는 경계하자. 유즈케이스가 많아질 때 추상화를 해도 늦지 않다.
ref - https://kentcdodds.com/blog/inversion-of-control
테스트코드 - 중첩을 지양하기
describe('테스트', ()=> {
beforeEach(()=> {
// 어쩌구..
});
describe('더 자세한 테스트', () => {
beforeEach(()=> {
// 어쩌구..
});
it('더 더 자세한 테스트' () => {
// 어쩌구...
});
})
})
중첩된 테스트를 피해야하는 이유
- 주로 테스트가 중첩되는 이유는 beforeEach 와 같은 셋팅을 공유해서인데,
- 이렇게 셋팅을 공유하다보면.. 내부에 있는 it 절의 테스트 코드에서 실제로 어떤 변수를 참조하는지 알기 위해 코드를 계속해서 추적해야한다.
- 또한 beforeEach 절을 사용하게 되면 어쩔 수 없이 변수의 재할당도 일어나는데, 이는 대표적인 안티패턴이다.
그러면 어떻게 해야할까? 인라인으로 작성하자.
- 즉, 중첩된 테스트들을 분리하여 각각의 테스트 내부에서 셋팅들을 공유하자.
- 즉, 각 테스트 그룹에 국한된 셋업들을 분리하여 작업하고 있는 코드에 대한 인지부하를 줄이자.
ref - https://kentcdodds.com/blog/avoid-nesting-when-youre-testing
테스트코드 - 세부구현은 테스트하지 않기
The more your tests resemble the way your software is used, the more confidence they can give you.
세부구현은 사용자가 알 수 없는 영역을 의미한다. 여기서 사용자란 개발자가 될 수도 있고, 실제 유저가 될 수도 있다.
세부구현을 테스트했을 때의 가장 큰 문제점은 아래와 같다.
- 리팩토링 했을 때에도 테스트가 실패 할 수 있다.
- 반대로, 코드의 로직이 변경되어 정상적으로 동작하지 않음에도 테스트가 성공 할 수 있다.
테스트코드는 동작에대해서만 테스트해야하지, 어떻게 구현 되었는지에 대해서 테스트해서는 안된다고 생각했다.
ref - https://kentcdodds.com/blog/avoid-the-test-user
Mist CSS 맛보기 😶🌫️
CSS in JS의 시대에서 Js from CSS라니! 따근따근한 프로젝트 궁금해서 한번 써봤당. 일반 css 문법을 사용하니 따로 러닝커브가 없다.
아래와 같이 mist.css 파일을 만들어준다.
- css의
@scope
문법을 사용하여 해당 class 내의 요소에만 css 를 적용한다. - 기본적으로 data-attribute 기반으로 스타일 배리언트를 처리해준다.
@scope (.button) {
button:scope {
/* Default style */
font-size: 1rem;
border-radius: 0.25rem;
&[data-size="lg"] {
font-size: 1.5rem;
}
&[data-size="sm"] {
font-size: 0.75rem;
}
&[data-danger] {
background-color: red;
color: white;
}
}
}
그리고 위와 같이 완성된 코드를 npx mistcss
로 빌드를 해주면
// Generated by MistCSS, do not modify
import './Button.mist.css'
type Props = {
children?: React.ReactNode
size?: 'lg' | 'sm'
danger?: boolean
} & JSX.IntrinsicElements['button']
export function Button({ children, size, danger, ...props }: Props) {
return (
<button {...props} className="button" data-size={size} data-danger={danger}>
{children}
</button>
)
}
짜잔 🤩 리액트 컴포넌트가 나온다.
-
css 파일의
@scope
내부 클래스는 프로젝트 내에서 유니크 한 값이어야한다.- @scope 내부에 주입한 클래스가 해당 컴포넌트의 className으로 주입되어있다. 그 이유로 유니크여야한 값이어야하는 것 같다.
- 요 부분은 라이브러리가 고도화 되면 강제되는 방향으로 발전하게 될 것 같다.
- 따라서 별도로 className 주입은 불가능하다.
-
컴포넌트의 props 들을 data-attribute로 주입한다. 따라서 css 코드에서 별도의 변수삽입 필요 없이 data-attribute만으로 스타일링 분기처리를 해줄 수 있다.
-
순수 css 코드만을 사용 할 수 있다보니 css의 이점을 잘 살릴 수 있다.
- 일단 zero config여서 너무 편하다.
- Data attribute를 prop으로 만들어준게 신기하다.
- 안정화가 된다면 간단한 UI 개발시에는 잘 써먹을 수도 있을 것 같다.
아무튼 정말 신박한 아이디어가 아닐 수 없다..😵
오렌지 주스 테스트
- 무리한 요구를 받았을 때 무조건 Yes or No 라고 대답하지 않기.
- 이를 위해 비용이 얼마나 필요하고, 대안은 이렇다 저렇다 대답하기 🍊
기획자의 ‘이거 가능한가요?‘라는 질문에 yes 라는건 실제로 구현 가능여부라기보다는 주어진 일정과 상황 내에서 ‘내’가 감당 가능한지 여부이다.
ref - https://johngrib.github.io/wiki/orange-juice-test/