[Spring] UserDao분리 실습으로 알아본 리팩토링과 메소드 추출

Dao의 분리 이유

개발자가 객체를 설계할 떄 가장 염두에 둬야 할 사항은 바로 미래의 변화를 어떻게 대비할 것인가 이다. 왜냐하면 오브젝트에 대한 설계와 이를 구현한 코드가 변하기 때문이다. 만약 2명의 개발자에게 동일한 기능 변경을 요청했다고 하자. 그런데 한 명은 단 몇줄의 코드만 수정하고 다른 개발자는 5시간이 걸렸다. 어떻게 변경이 일어날 때 필요한 작업을 최소화할까? 그것은 분리와 확장을 고려한 설계가 있었기 때문이다. 


변화가 한 번에 한 가지 관심에 집중돼서 일어난다면, 우리가 준비해야 할 일은 한가지 관심이 한 군데에 집중되게 하는 것이다. 즉 관심이 있는 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있게 하는 것이다.

프로그래밍의 기초 개념중에 관심사의 분리(Separation of Concerns)라는 게 있다. 이를 객체지향에 적용해보면, 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 관심이 다른 것은 가능한 한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것이라고 생각할 수 있다.

모든 것을 뭉뚱그려서 한데 모으는 편이 처음엔 쉽고 편하다. 그런데 언젠가는 그 뭉쳐있는 여러 종류의 관심사를 적절하게 구분하고 따로 분리하는 작업을 해줘야 할 때가 온다. 관심사가 같은 것끼리 모으고 다른 것은 분리해줌으로써 같은 관심에 효과적으로 집중할 수 있게 만들어주는 것이다.



UserDao의 관심사항


import user.domain.User;
 
public class UserDao {
    public void add(User user) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost/toby_spring""root""asdf");
    
        PreparedStatement ps = con.prepareStatement("insert into users(id, name, password) value(?,?,?)");
        
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());
        
        ps.execute();
        
        ps.close();
        con.close();
    }
    
    public User get(String id) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost/toby_spring""root""asdf");
 
        PreparedStatement ps = con.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();
        con.close();
        
        return user;
    }
}
 
cs


UserDao의 구현된 메소드를 살펴보자. 자세히 들여다보면 add() 메소드 하나에서 적어도 세 가지 관심사항을 발견할 수 있다.


1. DB와 연결을 위한 커넥션과 그와 관련된것

2. 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement만들고 실행하는 것

3. Statement와 Connection 오브젝트를 닫아줘서 소중한 공유 리소스를 시스템에 돌려주는것



여기서 가장 문제가 되는것은 첫째 관심사인 DB연결을 위한 Connection 오브젝트를 가져 오는 부분이다.  get() 과 add()메소드에 중복되기 때문이다. 앞으로 계속해서 DAO 메소드를 만들것인데 만들 때마다 중복된 코드를 넣어주는 것은 나중에 변경이 일어날 때 엄청난 고통을 일으키는 원인이 된다.



중복코드의 메소드 추출

가장 먼저 할 일은 커넥션을 가져오는 중복된 코드를 분리하는 것이다. 중복된 DB 연결 코드를 getConnection()이라는 이름의 독립적인 메소드를 만들어주자. 이렇게 분리하여 getConnection() 메소드를 호출해서 DB 커넥션을 가져오게 만든다.


public class UserDao {
    private static Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost/toby_spring""root""asdf");
        
        return con;
    }
    
    public void add(User user) throws SQLException, ClassNotFoundException {
        Connection con = UserDao.getConnection();
        
        PreparedStatement ps = con.prepareStatement("insert into users(id, name, password) value(?,?,?)");
        
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());
        
        ps.execute();
        
        ps.close();
        con.close();
    }
    
    public User get(String id) throws SQLException, ClassNotFoundException {
        Connection con = UserDao.getConnection();
        
        PreparedStatement ps = con.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();
        con.close();
        
        return user;
    }
    
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        UserDao dao = new UserDao();
        
        User user = new User();
        user.setId("kang5");
        user.setName("현수");
        user.setPassword("1234");
        
        dao.add(user);
        
        System.out.println(user.getId() + "등록 성공");
        
        User user2 = dao.get(user.getId());
        System.out.println(user2.getName());
    }
}
 
cs


커넥션을 분리해 줌으로써 앞으로 드라이버 클래스와 URL이 바뀌거나, 로그인 정보가 변경돼도 앞으로는 getConnection() 이라는 한 메소드의 코드만 수정하면된다.


이런식으로 기능이 추가되거나 바뀐 것은 없지만 UserDao는 이전보다 훨씬 깔끔해졌고 미래의 변화에 좀 더 손쉽게 대응할 수 있는 코드가 됐다. 이런 작업을 리팩토링(refactoring)이라고 한다. 또한 위에서 getConnection()이라고 하는 공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리택토링에서는 메소드 추출(extract method)기법 이라고 부른다.



이일민, 토비의 스프링 3, 에이콘

댓글

Designed by JB FACTORY