공부 일지/스터디 자료

[스터디] 클린 코드 스터디 1주차

Joshbla 2023. 6. 12. 16:06

클린 코드 스터디 1주차 : 1 ~ 3 장


1장 깨끗한 코드

[ C++창시자 비야네가 말하는 깨끗한 코드 ]

  1. 우아한 코드
    보는사람에게 즐거움을 주는 코드
  2. 효율성있는 코드
    코드자체의 속도나 메모리 뿐만이 아니라 나쁜코드를 작성해서 수정하는데 드는 비용을 줄여서 효율성을 챙겨야한다는 것

나쁜 코드는 나쁜코드를 유혹하고 낳는다
3. 철저한 오류 처리
세세한 사항까지 꼼꼼히 신경쓰라는 의미
4. 하나에 집중하라
너무 많은일을 하려고 애쓰다가 의도가 뒤섞이고 목적이 흐려진다.

[ 그래디 부치가 말하는 깨끗한 코드 : 가독성 ]

  1. 단순하고 직접적
  2. 설계자의 의도를 숨기지 않는다.
  3. 명쾌한 추상화와 단순한 제어문
    코드는 추측이 아니라 사실에 기반해야한다.
    반드시 필요한 내용만을 담아야한다.

[ 데이브토마스 말하는 깨끗한 코드 : 가독성 + 쉬운 수정 ]

읽기 쉬운 코드와 고치기 쉬운 코드는 다르다!
읽기 쉬운 것은 기본 + 고치기 쉬운 코드가 깨끗한 코드이다.

고치기 쉬운코드란 ?
작은 코드가 중요하다. 작은 단위의 테스트 케이스를 작성하라

그외…

  • 주의를 기울여서 꼼꼼하게 작성하라
  • 론 제프리스
  1. 모든 테스트를 통과한다.
  2. 중복이 없다.
  3. 시스템 내 모든 설계 아이디어를 표현한다.
  4. 클래스, 메서드, 함수 등을 최대한 줄인다.
  • 예상한대로 작동하는 코드
  • 작게 추상화 하라

[ 내가 생각해본 깨끗한 코드 ]

  1. 읽기 쉬운 코드
  2. 수정이 쉬운 코드
  3. 중복이 없는 코드
  4. 한 기능만을 수행하게 작성한 코드
  5. 직관적인 네이밍을 작성한 코드

2장 의미있는 이름

[ 의도를 분명히 밝혀라 ]

다음은 내가 프로젝트를 진행할 때 작성했던 코드이다.

public int findMyPrice(String token, long boardId) {
        Board board = find(boardId);
        Member member = memberService.findByAccessToken(token);
        Optional<BoardMember> optionalBoardMember = boardMemberRepository.findByBoardAndMember(board, member);
        if (optionalBoardMember.isEmpty()) {
            return 0;
        } else {
            return optionalBoardMember.get().getMyPrice();
        }
    }

findMyPrice는 어떤 상품에 내가 입찰한 가격을 알려주는 메서드이다.

board는 어떤 게시글을 의미하는가? member는 어떤 멤버를 의미하는 거지? optionalBoardMember는?

의미를 분명히하여 다시 작성해보자

public int findMyPrice(String token, long boardId) {
        Board board = find(boardId);
        Member loginedMember = memberService.findByAccessToken(token);
        Optional<BoardMember> myPriceInfo = boardMemberRepository.findByBoardAndMember(board, loginedMember);
        if (myPriceInfo.isEmpty()) {
            return 0;
        } else {
            return myPriceInfo.get().getMyPrice();
        }
    }

// 스터디원들의 추가 의견
// 이름은 그대로 하고 조건문을 옵셔널로 처리
// boardmember클래스의 이름을 아예 price로

[ 그릇된 정보를 피하라 ]

  1. 널리 쓰이는 단어를 다른 뜻으로 사용하지 마라
  2. 실제 해당 자료형이 아니면 사용을 피해라 List자료형이 아니라면
    멤버들의 집합이라고 memberList라고 사용하기 보다 memberGroup등 과 같이 사용하자
  3. 유사한 이름의 사용을 피하자

[ 의미있게 구분하라 ]

다음은 내가 알고리즘 문제를 풀 때 작성했던 코드이다.

문제를 빠르게 풀려다보니 변수명을 대충 작성했던 것 같다.

이것도 한 번 수정해보자.

// 큐브를 돌리는 메서드 udfblr은 돌리고 싶은 면 D는 시계방향, 반대방향을 뜻함
**public static void rotate(char udfblr, char D){

->>
public static void rotate(char face, char direction){**
**// 이중배열을 정렬하기위해 정의한 compare 함수
public int compare(int[] o1, int[] o2) {

->>
public int compare(int[] origin, int[] target) {**

[ 검색하기 쉬운 이름을 사용하라 ]

int[] reasonCnt = new int[5]; // 의도도 알 수 없고 검색을 5로 하기 어려움

->>
int weekDay = 5;
int[] reasonCnt = new int[weekDay];

[ 인코딩을 피하라 ]

변수명에 타입을 선언해줄 필요가 없다

String nameString; // 굳이 string이라고 알려줄 필요가 없다.

->>
String name;

접두어, 접미어의 사용도 줄이자 (인터페이스 클래스의 경우는 예외 ex impl)

[ 클래스 이름과 객체 이름은 명사 ]

메서드 이름은 동사**

접근 get~, 변경 set~, 조건 is~

[ 일관성있는 단어사용 ]

메서드를 set~로 했다가 update~로 했다 fetch~로 → X 의미가 같은 메서드라면 같은 단어를 사용하자

[ 의미있는 맥락을 추가하자 ]

String firstName;
String lastName;

위 같은 변수가 있다면 이게 사람의 이름인지.. 주소의 이름인지

String addressFirstName;
String addressLastName;

String memberFirstName;
String memberLastName;

명확히 맥락을 표현해주자 클래스를 따로 만들어주면 더 좋다.


3장 함수

[ 작게 만들어라 ]

내용이 길어지면 새로운 함수로 쪼개서 작게 더작게 .. if문 while문 등 에 들어가는 블록은 한줄로!

public void findPassword(MemberDto.Email dto) throws MessagingException {
    String email = dto.getEmail();
    if (memberRepository.countByEmail(email) == 0) {
        throw new ResponseStatusException(ExceptionCode.MEMBER_NOT_FOUND.getCode(), ExceptionCode.MEMBER_NOT_FOUND.getMessage(), new IllegalArgumentException());
    }
    Member member = findByEmail(email);
    String newPassword = randomPasswordService.genPassword();
    emailService.findPassword(email, newPassword);

    member.changePassword(passwordEncoder().encode(newPassword));
}

내가 과거에 작성했던 비밀 번호 찾기 로직이다. 에러를 던지는 부분을 따로 클래스, 메서드화해서 분리해도 좋을 것 같고 이메일이 존재하지 않는 상황은 자주 발생하기 때문에 if문 자체를 하나의 메서드로 뽑아내도 좋을 것 같다.

[ 한가지만 해라 ]

한가지만 하라? 한가지는 무엇인가 위의 코드에서 예를 들면

  1. 이메일을 받아온다
  2. 이메일이 있는지 확인한다
  3. 이메일을 통해 멤버를 가져온다
  4. 랜덤 비밀번호를 생성한다
  5. 이메일로 임시 비밀번호를보내준다
  6. 멤버의 패스워드를 임시 비밀번호로 변경한다

이러면 한가지가 아니라 6가지를 하는건가? 아니다 추상화 수준이 하나인 단계만 수행하면 된다.

위 코드를 수정해보면

public void findPassword(MemberDto.Email dto) throws MessagingException {
		memeber = getMemberByEmail(dto.getEmail());
   		String tempPassword = randomPasswordService.genPassword();
		emailService.findPassword(email, tempPassword );
		// 번외로 스터디원분이 emailService의 메서드이름도 변경하면 좋겠다는 의견
		// ->		emailService.sendTempPassword(email, tempPassword );
		member.changePassword(passwordEncoder().encode(tempPassword ));
}

public Member getMemberByEmail(String email) {
    return memberRepository.findByEmail(email)
            .orElseThrow(
                    () -> new ResponseStatusException(ExceptionCode.MEMBER_NOT_FOUND.getCode(), ExceptionCode.MEMBER_NOT_FOUND.getMessage(), new IllegalArgumentException())
            );
}

하나의 메서드에 8줄이 무식하게 들어있는게 아니고 3~4줄로 줄였다.

( 리팩토링 하다보니 getMemberByEmail 메서드를 작성해놓고 다른 곳에서만 사용하고 있었다.. 메서드를 잘확인하고 사용하자.. )

[ 추상화 수준을 하나로 ]

예시를 보면 맨처음 메서드는 “이메일을 받아와서 존재하는 계정인지 확인 후 존재한다면 계정 정보를 가져와 임시 비밀번호를 생성, 저장하고 이메일로 임시 비밀번호를 보내준다.” 의 기능을 수행하게 작성했다.

이는 추상화 수준이 높은 코드이다. 하나의 메서드에 많은 기능이 들어갔다.

 

수정 후 코드는 “이메일을 통해 계정정보를 가져와 임시 비밀번호를 생성, 저장하고 이메일로 임시 비밀번호를 보내준다.” + “이메일을 통해 존재하는 계정인지 확인 후 계정정보를 가져온다.” 의 기능으로 나눠서 추상화의 수준을 낮췄다.

 

만약 이보다 더 낮은 수준을 구사하려면

“이메일을 통해 계정정보를가져온다” + “임시 비밀번호를 생성한다” + “임시 비밀번호를 이메일로 보내준다” + “임시 비밀번호를 저장한다” 뭐 이런 느낌이려나

 

이런 수준을 모든 메서드에 일정하게 유지해야한다는 것이다.

[ Switch 문 ]

스위치문을 사용하게되면 srp, ocp 등 객체지향의 원칙을 위반할 수 있다.

더 큰 문제는 이런 스위치문이 하나가 아니라 여러개 존재할 수도 있다는 점이다.

이를 예방하기 위해 추상클래스를 만들어 해결한다.

추상클래스에서 스위치문으로 나누고 각 클래스에서 이 추상클래스를 구현한다.

[ 서술적인 이름을 사용하라 ]

2장에서 얘기한 내용

[ 함수 인수는 적게 사용 ]

인수는 적을 수록 좋다. 인수가 많아지고 자주 사용된다면 별도 클래스를 만드는 방법도 생각해보자

[ 명령과 조회를 분리하라 ] 

세팅하고 그 결과값을 리턴해주는 메서드는 지양하자.

public Board setBoard(){
	...
	boardRepository.save(board)
    return board;
}

분명 set이라고 해서 저장만하는 줄 알았는데?... 리턴값이있네?

명령메서드인가 조회 메서드인가?

[ 구조적 프로그래밍 ]

다익스트라의 구조적 프로그래밍 원칙에 따르면 함수에는 return문이 하나여야한다.

루프 안에서 break나 continue는 사용하지 않는게 좋다. 함수가 작다면 괜찮을 수도..