단일 책임 원칙이란 무엇입니까?

12694 단어
SOLID는 Uncle Bob이 발명한 약어이므로 클린 코딩을 위한 이러한 핵심 개념을 더 잘 기억할 수 있습니다. 첫 글자는 SRP(Single Responsibility Principle)를 의미합니다. SRP는 클래스나 함수가 한 가지만 담당해야 한다고 말합니다.

함수형 프로그래밍의 SRP



SRP는 하나의 클래스 또는 하나의 기능이 한 가지만 담당해야 한다고 말합니다. 예를 들어 데이터를 읽고 다른 데이터를 변경하는 기능이 있는 경우 변경해야 할 두 가지 이유가 있습니다. 그 이유는 데이터베이스에서 데이터를 읽거나 데이터를 변경하는 구현이 변경되면 전체 기능을 수정해야 하기 때문입니다.

이런 경우에 객체 지향 프로그래밍 언어에는 interface가 있습니다. 그러나 함수형 프로그래밍의 경우 일반적으로 함수형 프로그래밍 언어(FPL)에서 클래스를 사용하지 않기 때문에 이와 같은 구현이 없습니다.

Note: There is interface implementation in Typescript, so if you 
use Typescript for FPL, you can create an interface for 
SRP.

아래 예에서 SRP를 위반하는 함수의 잘못된 구현을 볼 수 있습니다.

Note: I'll use the React library to demonstrate the concept.



// bad example
let handleFetching = async () => {

    // 1. read data from somewhere
    let res = await fetch("some.url").then(res => res.json())

    if(res != null)
    {
        //2. mutate the state
        setData(res.data);
    }

}


handleFetching 함수에서 볼 수 있듯이 두 가지 책임이 있습니다. 첫 번째는 어딘가에서 데이터를 읽는 것이고 두 번째는 일부 상태 또는 데이터를 변경하는 것입니다. 따라서 이 두 가지 다른 책임에 대한 구현이 변경되면 handleFetching 기능을 변경해야 합니다. 또한 이 두 가지 책임을 테스트해야 하는 곳에서 이 기능을 테스트하기가 어렵습니다.

// good example
let handleFetching = async () => {

    // 1. read data from somewhere
    let res = await getData();
    return res
}

let getData = async () => {
// get data implementation goes here
}

useEffect(() => {
    let res = handleFetching()
    if(res != null)
    {
        //2. mutate the state
        setData(res.data);
    }
}, [])



좋은 예에서 볼 수 있듯이 모든 기능이 한 가지만 담당하도록 만들었습니다. handleFetching 함수는 이제 데이터 가져오기만 담당하고 일부 상태는 변경하지 않습니다. getData는 어딘가에서 데이터를 가져오는 등의 역할을 합니다. 따라서 우리는 모든 책임을 다른 기능으로 분리하여 개별적으로 테스트할 수 있습니다.

OOP의 SRP



함수형 프로그래밍과 유사하게, 우리는 함수가 한 가지를 담당하는지 여부를 결정하기 위해 함수를 분석할 수 있습니다. 기능이나 서비스에 대한 책임이 여러 개인 경우 해당 책임을 분리해야 합니다.

아래 코드에서 나쁜 예를 볼 수 있습니다.

Note: I'll use c# programming language to demonstrate the concept.



//bad example
public async Task<Something> GetSomethingById(int id)
{
    var something = await _somethingRepo.GetOneAsync(x => x.id == id);

    // violates the SRP 
    var url = "google.com";
    var client = new WebClient();
    var reply = client.DownloadString(url);
    something.reply = reply;


} 


보시다시피 GetSomethingById 메서드에서는 저장소에서 데이터를 가져오고 DownloadString 메서드를 사용하여 응답 속성을 업데이트합니다. 따라서 이 기능에는 두 가지 다른 책임이 있습니다. 하나는 데이터를 가져오는 것이고 다른 하나는 속성을 변경하는 것입니다.

// bad example
class FoodDelivery
{
    private List<Food> foods;

    public Food MakeFood(string name)
    {

    }

    public Food GetFood(int id, int price)
    {

    }

    public void DeliverFood(string path)
    {

    }

}


이 예에서 우리는 FoodDelivery 클래스에 세 가지 다른 메서드가 있고 모두 다른 논리에서 책임지는 것을 볼 수 있습니다. 구현을 변경하면 다른 기능에 영향을 미칠 수 있습니다. 예를 들어 MakeFood 메서드를 변경하면 DeliverFood 메서드 내부의 일부 로직을 업데이트해야 할 수 있습니다. 또한 MakeFoodGetFood 기능은 음식 배달 사업과 관련이 없습니다. 이제 논리를 다른 서비스로 분리하여 이 나쁜 예를 좋은 예로 바꾸는 방법을 살펴보겠습니다.

//good example
public interface IFoodGetter{
    Food GetFood(int id, int price);
}


public interface IFoodMaker{
    Food MakeFood(string name);
}

public class FoodGetter : IFoodGetter {
    public Food GetFood(int id, int price){
        //implementation goes here
    }
}

public class FoodMaker : IFoodMaker {
    public Food MakeFood(string name){
        //implementation goes here
    }
}

class FoodDelivery
{
    private List<Food> foods;
    private readonly IFoodGetter _foodGetter;
    private readonly IFoodMaker _foodMaker;

    public FoodDelivery(IFoodGetter foodGetter, IFoodMaker foodMaker){
        foodGetter = _foodGetter;
        foodMaker = _foodMaker;
    }

    public void DeliverFood(string path)
    {
        var food = _foodGetter.GetFood(1,20);

        if(food == null){
            food = _foodMaker.MakeFood("food_name");
        }

        //delivery implementation goes here

    }

}


위의 예에서 비즈니스 로직을 분리하기 위해 FoodGetter , FoodMaker 와 같은 두 가지 다른 서비스를 생성했습니다. 그리고 FoodDelivery 클래스는 음식 배달만 담당합니다. FoodGetter 서비스에서 일부 구현을 변경해야 하는 경우 해당 서비스 내에서 변경할 수 있습니다. 또한 인터페이스를 사용하여 이러한 서비스의 모의 구현을 생성할 수 있으므로 실제 데이터베이스 등에 연결하지 않고도 테스트할 수 있습니다.

좋은 웹페이지 즐겨찾기