회사 내부에서 스터디중에 React로 사고하기 단락에서 단일책임 원칙을 보았고, 개발원칙이 무엇이 있는지 살펴보았다.
이 글에서는 solid,dry,yangni,kiss에 대해 알아보겠다.
SOLID는 Robert C. Martin이 2000년 Design Principles and Design Patterns 논문에서 발표했고 Michael Feathers가 2004년에 SOLID란 말로 도입했다고 한다.
A class should have only one reason to change
클래스가 변경되는 이유는 오직 하나의 이유여만 한다.
function UsersComponent() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
axios
.get("https://reqres.in/api/users?page=2")
.then(res => setUsers(res.data.data))
.catch(err => console.log(err));
});
return (
<div>
{users.map(aUser => (
<li>
<span>
{aUser.first_name}::{aUser.last_name}
</span>
</li>
))}
</div>
);
}
해당 컴포넌트는 단일 책임 원칙을 위배한 사항이다.
데이터를 받음과 동시에 출력까지 진행하기 때문이다.
지금은 괜찮아 보이지만 더 많은 조건식이 추가되면 어떻게 될까?
function UsersComponent() {
...
return (
<div className="App">
{users.map(aUser => (
<li>
<span>
{aUser.first_name}::{aUser.last_name}
{ users.avatar ? (
...Show avatar
...Show some action for user
) : (
....Some Psuedo Code
)}
</span>
</li>
))}
</div>
);
}
벌써부터 눈이 아프기 시작했다.. SRP 원칙을 적용해보자
function UsersComponent() {
...
return (
<div className="App">
<userList user={user}/>
</div>
);
}
userList Component
function usersList(props) {
const uploadAvatar = () => {
console.log("implement update avatar");
};
return (
<div>
{props.users.map(aUser => (
<li>
<span>
{aUser.first_name}::{aUser.last_name}
</span>
{aUser.avatar ? (
<img src={aUser.avatar} alt="" />
) : (
<button onClick={uploadAvatar}>Upload avatar</button>
)}
</li>
))}
</div>
);
}
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
소프트웨어는 확장에는 열려있고 수정에는 닫혀있어야한다.
하나의 객체에서 무언가 추가되어야할때 해당 객체를 수정하는것이 아닌 인터페이스나 다른 부수적인 부분을 통해 확장을 해야된다는 말이다.
objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
프로그램의 개체는 해당 프로그램의 정확성을 변경하지 않고 하위 유형의 인스턴스로 대체 할 수 있어야합니다.
함수형 프로그래밍에서는 사용하지는 않지만, 알아보자면 인터페이스와 같이 상속의 개념이 들어가는 부분에서는
부모 인터페이스를 좀 더 묶음적으로 작성하라는 말이다.
Clients should not be forced to depend on methods they do not use.
클라이언트가 사용하지 않는 인터페이스에 의존하도록 강요해서는 안됩니다.
즉 사용하지 않는 것들은 구현하지 말아야된다는 말이다.
인터페이스를 구성할때 좀 더 세분화되도록 짜라는 말이지만, react에서는 typescript를 사용하면 간단히 해결 할 수 있다. typescript에서 ?를 사용하지 않는 이상 해당 props는 사용될 수 없기 때문이다.
we should depend upon abstractions, not concretions.
우리는 결론이 아니라 추상화에 의존해야합니다.
하나의 함수가 결론만 낸다면 어떻게 될까? 당장에야 코드 규모가 작을때는 읽기 쉽고, 유지보수가 쉬울테지만
코드가 길어진다면 코드 고치는 시간보다 코드를 분석하는 시간이 더 길어질 것이다.
아래 코드로 살펴보자
const fetchUsers = async () => {
const userResponse = await fetch('userURL');
const userData = await userResponse.json();
const userAuth = await fetch('userAuthURL');
const userAuthData = await userAuth.json();
}
두가지의 api연결하는데에도 위와 같은 코드가 나온다. 하물며 더 많은 로직이 들어간다면 어떻게 될까? 유지보수는 고사하고 분석에 시간을 쏟게 될 것이다.
위의 코드를 다음과 같이 고칠 수 있을 것이다.
const fetchUsers = async () => {
const userData = await userData.user
const userAuthData = await userData.auth
}
function userData() {
const user = async() => {
const data = await fetch('userURL');
return data.json();
}
const auth = async() => {
const data = await fetch('userURL');
return data.json();
}
}
Do not Repeat Yourself
반복하지마라
eslint를 사용한다면 duplicate란 문자를 볼 수 있을 것이다. 시간에 쫒겨 공통 모듈로 빼지 않고,
복붙 하는 경우가 있는데.. 최대한 지양하면서 개발을 해야겠다.
You aren’t gonna need it
너는 그것을 필요하지 않을 것이다.
미리 짜지 말라는것이다. 현재에 집중하고, 미래를 위해 생각하지 말라는 것이다.
개발을 하다보면 미리 하겠지란 생각에 코드를 작성한적이 있는데, 나중에갔을때 보면 해당 설계를 완전히 뒤집을때도 빈번하다. 따라서 현재에 집중하자
Keep It Simple Stupid
간단하게 생각해라 불필요하게 코드가 장황해진다면 이것또한 불필요한 시간에 에너지를 쏟게된다.
따라서 최대한 단순하게 생각하자
오늘은 개발 원칙에 대해 알아보았다.
solid는 논문으로도 나올정도로 방대한 이론이고, 아직 더 공부중이다. 또한 다른 개발원칙을 찾아보았지만 개발원칙에서의 모든 결론은 추상화를 잘하고, 사용하지 않는것은 미리 만들지 말자 였던것 같다.
마지막으로 다음 글을 꼭 읽어 보길 추천한다. solid 원칙뿐만 아니라 if else vs switch 등 여러가지 좋은 글들을 많이 볼 수 있다.
Robert C. Martin의 Clean Code Blog
The Single Responsibility Principle
Single-responsibility principle (Wiki)
The Open-Closed principle
Devon-SOLID
React-SOLID