Spring/스프링의 이해와 원리

오브젝트와 의존관계 - 2. 관심사의 분리 + 리팩토링

JWonK 2022. 3. 9. 18:08
728x90
반응형

전 게시글에서 만들어본 UserDao 클래스이다.

package jpaEx.toyP.tobispring;

import javax.xml.transform.Result;
import java.sql.*;

public class UserDao1 {

    public void add(User user) throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/springbook", "spring", "book");

        PreparedStatement ps = c.prepareStatement(
                "insert into users(id, name, password) values(?,?,?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());

        ps.executeUpdate();

        ps.close();
        c.close();
    }

    public User get(String id) throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/springbook", "spring", "book");

        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));

        rs.close();
        ps.close();
        c.close();

        return user;
    }
}

객체지향 설계가 들어가지 않은 최악의 코드이다. 이 코드를 어떻게 수정해야할지 살펴본다.

 

 

분리와 확장을 고려한다.

먼저, 분리에 대해 생각해보자. 책에 있는 문구를 가져와보면

 

"DB를 오라클에서 MySQL로 바꾸면서, 웹 화면의 레이아웃을 다중 프레임 구조에서 단일 프레임에 Ajax를 적용한 구조로 바꾸고, 매출이 일어날 때에 지난 달 평균 매축액보다 많으면 감사 시스템의 정보가 웹 서비스로 전송되는 동시에 로그의 날짜 포맷을 6자리에서 Y2K를 고려해 8자리로 바꿔라"

 

라는 요구사항이 떨어진다고 가정해보면 모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다.

 

그렇기에 더욱 객체지향 프로그래밍의 중요성이 강조된다. 위 코드처럼 관심사항을 분리하지 않고 한 코드에 모든 것을 작성하게 되면 코드를 수정할 때 전체 코드를 수정해야하는 불편한 상황이 발생한다.

 

그렇기 때문에 객체 지향을 적용하여 관심사를 분리하고 수정사항이 생길 경우에는 해당 객체(클래스)만 수정할 수 있도록 한다. 

 

관심사의 분리(Separation of Concerns) : 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게
하고, 관심이 다른 것은 가능한 한 따로 떨어져서 서로 영향을 주지 않도록 분리

 

 

그럼 전에 만든 UserDao의 구현된 메소드를 다시 보면, 관심사항을 분리할 수 있다.

 

1. 첫째는 DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심이다. 더 세분화해서 어떤 DB를 쓰고, 어떤 드라이버를 사용할 것이고, 어떤 로그인 정보를 쓰는데, 그 커넥션을 생성하는 방법은 또 어떤 것이다라는 것까지 구분해서 볼 수도 있다. 세부관심까지 분류하면 너무 복잡해지므로 일단은 뭉뚱그려서 DB 연결과 관련된 관심이 하나라고 보자. 이것만 해도 여타 관심사항과는 명확히 분리가 가능하다.

 

2. 둘째는 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행하는 것이다. 여기서의 관심은 파라미터로 넘어온 사용자 정보를 Statement에 바인딩시키고, Statement에 담긴 SQL을 DB를 통해 실행시키는 방법이다. 파라미터를 바인딩하는 것과 어떤 SQL을 사용할지를 다른 관심사로 분리할 수도 있기도 하지만, 우선은 이것도 하나로 묶어서 생각한다.

 

3. 셋째는 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아줘서 소중한 공유 리소스를 시스템에 돌려주는 것이다.

 


 

가장 문제가 되는 것은 첫째 관심사인 DB 연결을 위한 Connection 오브젝트를 가져오는 부분이다. 앞으로 수백, 수천 개의 DAO 메소드를 만들게 될지 모르는데, 그렇게 된다면 DB 커넥션을 가져오는 코드가 여기저기에 계속 중복돼서 나타날 것이다. 바로 이렇게 하나의 관심사가 방만하게 중복되어 있고, 여기저기 흩어져 있어서 다른 관심의 대상과 얽혀 있으면, 변경이 일어날 때 엄청난 고통을 일으키는 원인이다.

 

중복 코드의 메소드 추출

package jpaEx.toyP.tobispring;

import javax.xml.transform.Result;
import java.sql.*;

public class UserDao1 {

    public void add(User user) throws ClassNotFoundException, SQLException{
        Connection c = getConnection();

        PreparedStatement ps = c.prepareStatement(
                "insert into users(id, name, password) values(?,?,?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());

        ps.executeUpdate();

        ps.close();
        c.close();
    }

    public User get(String id) throws ClassNotFoundException, SQLException{
        Connection c = getConnection();

        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));

        rs.close();
        ps.close();
        c.close();

        return user;
    }
    
    // DB 연결 기능이 필요하면 getConnection() 메소드를 이용하게 된다.
    // 중복된 코드를 독립적인 메소드로 만들어서 중복을 제거해준다.
    private Connection getConnection() throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/springbook", "spring", "book");
        
        return c;
    }
}

 

지금은 UserDao 클래스의 메소드가 두 개이지만 메소드가 1,000개쯤 된다고 생각해보자.

DB 연결과 관련된 부분에 변경이 일어났을 경우, 예를 들어 DB종류와 접속 방법이 바뀌어서 드라이버 클래스와 URL이 바뀌었다거나, 로그인 정보가 변경돼도 앞으로는 getConnection()이라는 한 메소드의 코드만 수정하면 된다.

 

 


방금 위에서 한 작업은 UserDao의 기능에는 아무런 변화를 주지 않았다. 여전히 사용자 정보를 등록하고 조회하는 조금 난간함 DAO 클래스일 뿐이다. 하지만 중요한 변화가 있었다. 앞에서 한 작업은 여러 메소드에 중복돼서 등장하는 특정 관심사항이 담긴 코드를 별도의 메소드로 분리해낸 것이다.

 

이 작업은 기능에는 영향을 주지 않으면서 코드의 구조만 변경한다. 기능이 추가되거나 바뀐 것은 없지만 UserDao는 이전보다 훨씬 깔끔해졌고 미래의 변화에 좀 더 손쉽게 대응할 수 있는 코드가 됐다. 이런 작업을 리팩토링(refactoring)이라고 한다.

 

또한, 위에서 사용한 getConnection()이라고 하는 공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는 메소드 추출(extract method) 기법이라고 부른다.

 

 

 

728x90
반응형