본문 바로가기
Study/디자인 패턴의 아름다움

[디자인 패턴의 아름다움] 4. 코딩 규칙 - 내용 정리

by Nahwasa 2024. 4. 24.

스터디 메인 페이지

목차

    - ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한대로 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다.

    - 모든 이미지의 출처는 디자인패턴의 아름다움(왕정 저) 책 입니다.

     


     

    CHAPTER 04. 코딩 규칙

    4.1 명명과 주석

    명명

    • 함수의 임시 변수와 같이 사용 범위가 비교적 작은 변수의 경우, a, b, c와 같은 짧은 이름을 사용할 수 있다. 하지만 전역 변수와 같이 범위가 큰 변수의 경우 긴 이름을 사용하는 것이 좋다.
      • 한눈에 설계 의도를 파악할 수 있기 때문이다.
    • 이하의 코드에서 굳이 위의 User 클래스처럼 user 접두사를 사용할 필요는 없다. 아래의 User 클래스처럼 멤버 변수 이름을 지정하면 된다.
    public class User {
    	private String userName;
        private String userPassword;
        ...
    }
    
    // 이렇게 사용하면 된다.
    public class User {
    	private String name;
        private String password;
    }
    
    // 사용하는 쪽에선 아래처럼 될꺼다.
    user.getUserName(); -> user.getName();

     

    • 대부분의 비즈니스 개발에는 많은 비즈니스 명사가 포함된다. 동일한 비즈니스 용어의 번역이 프로그래머의 영어 이해 수준에 따라 번역이 다를 수 있고 그 결과 코드의 가독성도 떨어진다. 동일한 비즈니스 용어집을 준비해두면 이 문제를 효과적으로 해결할 수 있다.
    • 명명은 정확하지만 추상적이어야 한다. 구현 세부 사항을 너무 구체적으로 명시하지 말고 작업 방법이 아닌 작업 자체만 명시하면 된다.
      • ☆ 180쪽의 맨 아래 코드도 아래처럼 명명을 바꾸는게 더 추상적이고 좋은 것 같다.
    public void uploadUserAvatarImageToAWS(...);
    ->
    public void uploadUserAvatarImage(...);

     

     

    주석

    • 주석은 명명만큼 중요하다. 일부 프로그래머는 좋은 명명이 주석을 완전히 대체할 수 있으며, 만약 코드에 주석이 필요하다면 명명이 충분히 적합하지 않다는 뜻이므로, 주석을 추가하는 대신 명명을 다시 고려해야 한다고 말한다. 하지만 이러한 견해는 매우 편향적일 수 있다.
    • 명명을 아무리 잘 하더라도 결국 길이 제한이 있을 뿐만 아니라 모든 것을 담을 수는 없으므로, 주석은 매우 좋은 보충 설명이 될 수 있다.
    • 주석은 주로 무엇(what)을 하는지, 왜(why) 하는지, 어떻게(how) 하는지 3가지 측면을 담고 있다.
    /**
     * (what) Beaen을 생성하는 데 사용되는 팩터리 클래스
     * (why) 이 클래스의 기능은 Spring IoC 프레임워크와 비슷하지만 더 가볍다.
     * (HOW) 다른 데이터 소스에서 다음 순서로 Bean을 생성한다.
     * 사용자 지정 객체 -> SPI -> 설정 파일 -> 기본 객체
    */
    public class BeanFactory {...}

     

    • ☆ 여러모로(?) 유명한 클린코드 책에선 이 책과 약간 반대 입장을 취하는 뉘앙스이다. 내 경우엔 양측의 의견이 모두 동감된다. 사실 아직 이에 대한 명확한 내 인사이트가 없다. 개인적으로는, "코드가 변경될 때 마다 그에 따라 주석도 그에 맞춰 수정할 수 있는 경우엔 주석을 쓰는게 당연히 좋다" 라고 생각한다. 근데 보통 이렇게 하는건 많이 힘들다. 보통 개발팀과 운영팀이 분리되어 있고, devOps로 개발+운영은 경험해보니 많이 어려웠다.
    • 그래서, 프레임워크 급의 대규모 개발이고 팀원들의 실력도 모두 충분히 뛰어나며, 유지보수팀까지 케어할 수 있는 경우라면 이 책의 내용이 맞다고 생각되고, 한번 해보고 싶다. 근데 일반적인 수준의 프로젝트라면 난 클린코드에 나온 다음의 내용이 더 공감된다. "코드에 주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문이다. 자신이 저지른 난장판을 주석으로 설명하려 애쓰는 대신에 그 난장판을 깨끗이 치우는 데 시간을 보내라! (클린코드)". 물론 당장은 이게 더 공감되긴한데, 언제든 바뀔 수 있다. 현재는 내 실력이 아직 못따라와서 내 코드에서 그런 예시를 많이 보니 공감되는걸꺼다.
      • ☆ 물론 이 책 3장에 나온 kmp 알고리즘 같은거 짜놓고, 의도를 표현하는 주석 없는건 분명 잘못된거다 ㅋㅋ

     


     

    4.2 코드 스타일

    • 굳이 표준적인 코드 작성 방법을 추구할 필요도 없다. 중요한 것은 팀이나 프로젝트 전체에서 코드 스타일을 일관되게 유지하는 것이다.
      • ☆ 개인적으로도 가장 중요한건 명분과 팀의 합의라 생각한다.
    • 클래스나 함수가 너무 작으면 그만큼 클래스나 함수의 개수가 늘어나기 때문에 호출 관계가 복잡해지며, 특정 코드의 논리를 확인하기 위해 여러 클래스와 함수 사이를 이동해야 하므로 가독성에 큰 영향을 미친다.
    • 한 줄의 적절한 길이 : 가급적 IDE의 표시 너비를 초과해서는 안 된다.
    • 빈 줄을 상죵하여 각 코드 블록을 구분할 수도 있다.

     


     

    4.3 코딩 팁

    • 일반적으로 함수의 매개변수가 5개 이상이면 너무 많다고 말할 수 있다. 
      • [A] 함수를 여러 함수로 분할하여 매개변수를 줄일 수 있다.
      • [B] 혹은 매개변수를 객체로 캡슐화하는 방법으로 해결할 수 있다.
    // [A]
    public User getUser(String id, String username, String telephone, String email, String udid, String uuid);
    
    ->
    public User getUserById(String id);
    public User getUserByUsername(String username);
    public User getUserByTelephone(String telephone);
    ...
    
    
    // [B]
    public void postBlog(String title, St4ring summary, Stering keywords, String content, String category);
    
    ->
    public void postBlog(Blog blog);
    
    public class Blog {
    	private String title;
        private String summary;
        private String keywords;
        ...
    }

     

    • ☆ 위에서 '일반적으로' 매개 변수 5개 이상이면.. 이라고 했는데, 일반적이지 않은 경우는 예를들어 자바의 List.of() 가 있다. 자바 효율성 향상을 위해, 매개변수 10개까진 미리 정의해두었고, 그 이상이 되야 varargs로 처리한다.

     

    • 함수에서 bool 또는 boolean 형식의 플래그 매개변수를 통해 값을 받고, 이 값이 true 일 때와 false일 때 실행하는 코드가 다르도록 설계하면 안 된다.
    • 너무 깊은 중첩 코드는 종종 if-else, switch-case, for 반복문의 과도한 중첩으로 인해 발생한다. 일반적으로 중첩은 2단계를 넘지 않는 것이 좋다. 이하 처리 방법
      • 중복되는 if, else 제거
      • continue, break, return 키워드를 사용하여 중첩을 바로 종료
      • 실행 순서를 조정하여 중첩 단계를 줄인다.
      • 중첩 단계를 줄이기 위해 코드의 일부를 함수로 캡슐화한다.
    • ☆ 개인적으로도 이전에 회사에서 누가 한 50줄정도 되는 if와 for로 구성된 엄청난 코드를 보고 있는걸 지나가다가 봤다. 내 생각에 절대 그런식으로 코드가 짜질 것 같지 않아서, 그 코드를 받아서 로직 정리하니 5줄로 동일하게 처리 가능했다.
    • 중첩 관련해서 저자에게 '하이퍼 토마토(백준 17114)' 문제를 짜보게 시켜보고 싶었다. 11차원 배열에서의 BFS 알고리즘 구현이 필요한 멋진(?)문제로, 일반적으론 구현에 11중 반복문이 필요하다. ㅋㅋ
    • 설명 변수는 코드 가독성을 향상시키고 불필요한 주석을 줄일 수 있다.
    if (date.after(SUMMER_START) && date.before(SUMMER_END)) {...}
    
    ->
    boolean isSummer = date.after(SUMMER_START) && date.before(SUMMER_END);
    if (isSummer) {...}

     

    • ☆ 친구가 배열에서 max값을 bit로 찾는 것 관련해서 얘기하길래, 몇일 전에 심심해서 걔가 말한대로 구현해줬었다. 여기도 hasPositiveNum이라는 설명변수가 있었는데, 사실 이게 '설명변수'라고 불리는걸 처음 알았다! 신기했다. 아래는 그 코드인데, 진짜로 쓸모없는 코드이다. 효율성 별로다. (입력이 정수 최소값일 경우 제대로 동작 안한다. 여기까지 예외처리하긴 귀찮았다.)
    import java.util.List;
    
    public class Main {
    
        public static void main(String[] args) {
            List<Integer> list = List.of(-3, -2, -1, 4);
            System.out.println(new Main().uselessBitMaxChecker(list));
        }
    
        private int uselessBitMaxChecker(List<Integer> list) {
            int positiveCnt = 0;
            for (int cur : list) {
                if (cur >= 0) positiveCnt++;
            }
            boolean hasPositiveNum = positiveCnt > 0;
    
            int[] arr;
            if (hasPositiveNum) {
                arr = new int[positiveCnt];
                for (int cur : list) {
                    if (cur < 0) continue;
    
                    arr[--positiveCnt] = cur;
                }
            } else {
                arr = new int[list.size()];
                int idx = 0;
                for (int cur : list) {
                    arr[idx++] = Integer.MAX_VALUE + cur;
                }
            }
    
            int shift = 31;
            boolean[] invalid = new boolean[arr.length];
            while (--shift>0) {
                boolean isOneExist = false;
    
                for (int i = 0; !isOneExist && i < arr.length; i++) {
                    if (invalid[i]) continue;
    
                    isOneExist = (arr[i] & 1<<shift) != 0;
                }
    
                if (!isOneExist) continue;
    
                for (int i = 0; i < arr.length; i++) {
                    if ((arr[i] & 1<<shift) == 0) {
                        invalid[i] = true;
                    }
                }
            }
    
            int i = 0;
            while (invalid[i]) i++;
    
            int max = arr[i];
    
            if (hasPositiveNum) return max;
    
            return max-Integer.MAX_VALUE;
        }
    }

    댓글