왔다 갔다 다시: OO를 FP로 리팩토링
11701 단어 nodetypescriptfunctionaloop
일반적인 생각과는 달리 OO와 FP는 보이는 것보다 더 가깝다고 생각합니다. 적어도 이것은 OO 코드가 SOLID 설계 원칙을 염두에 두고 작성된다면 특히 사실인 것 같습니다.
이 기사에서는 Typescript를 사용하여 SOLID 객체 지향(OO) 코드에서 보다 기능적인 프로그래밍(FP) 스타일로 리팩토링하는 방법을 살펴보겠습니다. "방법"측면 외에도 테스트 가능성 관점에서 각 리팩토링을 살펴보겠습니다. 나는 그것이 코드 품질의 좋은 척도라는 것을 알았습니다. 테스트하기 쉽다면 이상한 상태나 숨겨진 종속성이 없을 가능성이 높습니다.
더 이상 고민하지 않고… 리팩토링하자!
이 예에서는 매우 간단한 은행 계좌 예를 사용합니다.
Account
도메인 개체가 있고 사용 사례는 새 계정을 여는 것입니다.interface Account {
id: string;
name: string;
accountStatus: 'OPEN' | 'CLOSED';
}
interface AccountDao {
save: (account: Account) => Promise<Account>;
}
class AccountService {
constructor(readonly accountDao: AccountDao) {}
public async openAccount({
id = uuid(),
name,
}: {
id?: string;
name: string;
}) {
const account: Account = { id, name, accountStatus: 'OPEN' };
return this.accountDao.save(account);
}
}
이 예제에서 볼 수 있듯이 이것은 매우 일반적인 SOLID 코드입니다. 사용 사례에 대한 비즈니스 규칙을 포함하는 상태 비저장 서비스 클래스가 있으며 계정 정보를 유지할 수 있도록 데이터 계층에 대한 종속성을 유지합니다. 메모리 내 데이터베이스 또는 모의를 사용하여 가짜 구현을 주입할 수 있으므로 쉽게 테스트할 수 있습니다.
FP에 대한 첫 번째 리팩토링에서 실제로 이것을 함수로 만들어야 합니다. 그리고 그들이 말했듯이 "폐쇄는 가난한 사람의 목적입니다."따라서 이것을 기능적 폐쇄로 바꾸겠습니다.
export const accountService = (accountDao: AccountDao) => {
const openAccount = ({
id = uuid(),
name,
}: {
id?: string;
name: string;
}) => {
const account: Account = {
id,
name,
accountStatus: 'OPEN',
};
return accountDao.save(account);
};
return { openAccount };
};
우리는 아직 기능적입니까? 좀 빠지는. 우리는 여전히 이 반복에서 개인 상태를 잠재적으로 유지할 수 있으므로 클로저를 제거하고 고차 함수를 가져옵니다.
export const openAccount = ({
id = uuid(),
name,
saveAccount,
}: {
id?: string;
name: string;
saveAccount: AccountDao['save'];
}) => {
const account: Account = {
id,
name,
accountStatus: 'OPEN',
};
return saveAccount(account);
};
이봐, 이것은 꽤 멋지다, 우리는 종속성을 함수에 직접 전달하고 있으며, 클로저에서 상태를 유지하고 테스트할 수 있는 기능을 모두 제거했습니다. 하나의 메서드와 내장 생성자가 있는 인터페이스처럼 느껴집니다. 나는 그것을 파다.
그래도 할 일이 있습니다. 종속성을 모두 함께 고려할 수 있습니까? 먼저 계정 개체를 생성하고 자체 기능으로 추출할 수 있습니다.
export const createAccount = ({
id = uuid(),
name,
}: {
id?: string;
name: string;
}): Account => ({
id,
name,
accountStatus: 'OPEN',
});
이제
createAccount
함수가 순수함을 알 수 있습니다. 그리고 인터페이스에 의존하는 대신 saveAccount
함수 구현을 직접 작성할 수 있습니다.export const saveAccount = async (
account: Account
): Promise<Account> => {
await fs.promises.writeFile(
'/accounts-store/accounts.txt',
JSON.stringify(account)
);
return account;
};
마지막으로 유스 케이스를 만족시키기 위해 두 가지를 구성할 수 있습니다.
export const openAccount = ({
id = uuid(),
name,
}: {
id?: string;
name: string;
}): Promise<Account> => saveAccount(createAccount({ id, name }));
하지만 잠깐만요, 이것이 어떻게 테스트 가능합니까!? 가짜
dao
를 함수에 주입할 수 없습니다. 여기서 대답은 구성을 단위 테스트하지 않는다는 것입니다. 대신 우리는 매우 간단한 순수한 부품을 단위 테스트합니다. 전체 구성을 테스트하려면 통합 테스트(이름에 대한 진정한 증거)가 필요합니다.결국 목표는 OO나 FP의 결정이 아니라 명확한 책임과 제한된 결합을 가진 상태 비저장 프로그래밍에 있습니다.
인생의 대부분이 그렇듯이 흑백도 전부는 아닙니다. 이러한 모든 리팩토링은 처음부터 실행 가능했습니다. 각각은 상태가 없고 테스트 가능하며 명확한 책임이 있습니다! 여기서 주요 차이점은 종속성 역전 또는 종속성 거부를 사용한 종속성 관리입니다.
균형이 중간 어딘가에 있다고 결론을 내리고 싶습니다. 개인적으로 저는 고차 함수 리팩토링을 선호합니다. 그것은 다음과 같은 점에서 두 세계의 장점을 모두 가지고 있는 것 같습니다.
FOOP이라는 새로운 패러다임을 발명할 수 있을까요? 읽어 주셔서 감사합니다!
Reference
이 문제에 관하여(왔다 갔다 다시: OO를 FP로 리팩토링), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/hallsamuel90/there-and-back-again-refactoring-oo-to-fp-l8l텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)