개발교양도서/클린코드

[클린코드] 한 가지 추상화, 한 가지 추상화 수준 [ 함수 ]

IT록흐 2021. 7. 30. 01:14
반응형
 

Clean Code

『CLEAN CODE(클린 코드)』은 오브젝트 멘토(OBJECT MENTOR)의 동료들과 힘을 모아 ‘개발하며’ 클린 코드를 만드는 최상의 애자일 기법을 소개하고 있다. 소프트웨어 장인 정신의 가치를 심어 주며

book.naver.com

 

 

'클린코드'의 저자 로버트 마틴은 말한다.

 

함수는 작은 함수가 좋다고 확신한다. 

 

작은 함수는 한 가지 기능만 한다. 만약 함수가 두 가지 기능으로 설명된다면 이미 큰 함수이다. 여기서 한 가지 기능이란, 한 가지 '추상화'를 의미한다. 

 

한 가지 추상화

 

아래 코드는 회원가입을 수행하는 함수이다. 회원가입 버튼을 누르면 해당 함수가 실행된다. 회원가입은 세 가지 작업으로 세분화된다.

 

1. ID 유효성 검사

2. DB에 저장

3. 페이지 전환

 

정리하면,

한 가지 기능은 회원가입이다. 그리고 회원가입은 세 가지 기능으로 세분화된다. 그러므로 세 가지 기능 또한 각각 한 가지 함수로 표현되어야 한다. 이와 같이, 하나의 기능 하나의 함수로 만드는 작업을 '추상화'라 부른다. 

 

아래 코드는 추상화하지 않고 여러 기능을 무질서하게 늘어놓은 코드이다. 그래서 함수 한 개의 길이가 굉장히 길다. 

 

< 개선 전 코드 >

 ( 주석의 관심사는 추상화를 의미한다. ) 

	@Override //회원 가입 기능
	public void mainButtonAction()  {
		String userName = userNameText.getText();
		
		// 관심사 : userName 유효성 검사 ( 공백 )
		if(userName == "") {
			userNameCheck.setText("닉네임이 공백입니다.");
			userNameCheck.setVisible(true);
		}
		// 관심사 : userName 중복 검사 및 DB 삽입
		else {
			try {
				// 관심사 : DB 중복 체크 
				Dao dao = new Dao();
				int checkDuplicateResult = dao.checkDuplicateUserName(userName);
				
				// 1. 중복이 안된 경우
				if( checkDuplicateResult > 0) { // 관심사 : userName 중복 검사 
					
					// 관심사 : 로그인시 필요한 Pem 파일 만들기
					GeneratingKey generatingKey = new GeneratingKey();// 관심사 : 개인키, 공개키 생성
					String localhost ="localhost:"+ (5500 + (int)(Math.random()*100));// 관심사 : 주소 생성
					Pem pem = new Pem(userName); // 관심사 : Pem 파일 만들기
					pem.makePrivateAndPublicPemFile(generatingKey.getPrivateKey(), generatingKey.getPublicKey());
					
					// 관심사 : Peer 정보 DB 저장
					int joinResult = dao.join(localhost, userName);
					// 회원정보 DB 저장 성공한 경우
					if(joinResult > 0) {
							// 페이지 전환
							newPage = new NewPage("/view/login.fxml", stage);
							newPage.createPageOnCurrentStage();
							newPage = new NewPage("/view/popup.fxml", stage);
							Controller controller = newPage.getController();
							controller.setObject("회원가입이 완료되었습니다.");
							newPage.createPageOnNewStage();
					}
					// 회원정보 DB 저장 실패한 경우
					else {}	
					
				// 2. 중복된 경우 
				}else if(checkDuplicateResult == 0) {
					userNameCheck.setText("닉네임이 중복됩니다.");
					userNameCheck.setVisible(true);
				// 3. SQL문 실행 문제 발생 
				}else {}
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

 

 

회원가입은 3가지 기능을 갖는다. 그러나 3가지 기능을 추상화하지 않고 그저 코드로 늘어놓다보니 함수가 무자비하게 길어졌다. 로버트 마틴은 하나의 함수는 한 가지 추상화를 담으므로 한 번의 들여쓰기로 표현하기를 추천한다.

 

이를 한번 작은 함수로 클린하게 바꾸어 보자.

 

 

< 개선 후 코드 >

	// 관심사 : 회원가입
    	@Override
	public void mainButtonAction() throws IOException  {
		if(!isEmptyUserName()) doJoinProcess(duplicateCheck());
		else setUserNameCheck("닉네임이 공백입니다.");	
	}
	// 관심사 : userName 공백체크
	private boolean isEmptyUserName() {
		this.userName = userNameText.getText();
		if(userName == "") return true;
		else return false;
	}
	// 관심사 : 회원가입 작업진행
	public void doJoinProcess(int duplicateResult) throws IOException {
			if(duplicateResult > 0) { // 중복이 안된 경우
				makePemFile(); // Pem파일생성
				addPeerInDB(); // 회원정보 DB저장
			}else if(duplicateResult == 0) { // 중복된 경우 
				setUserNameCheck("닉네임이 중복됩니다.");
			}else {} // SQL문 실행 문제 발생 
	}
	// 관심사 : userName 중복체크
	public int duplicateCheck() {
		int result = dao.checkDuplicateUserName(this.userName);
		return result;
	}
	// 관심사 : 로그인시 필요한 Pem 파일 만들기
	private void makePemFile() {
		GeneratingKey generatingKey = new GeneratingKey();// 관심사 : 개인키, 공개키 생성
		Pem pem = new Pem(this.userName); // 관심사 : Pem 파일 만들기
		pem.makePrivateAndPublicPemFile(generatingKey.getPrivateKey(), generatingKey.getPublicKey());
	}
	// 관심사 : 회원정보 DB에 저장하기 + 화면전환하기 ( 두 개의 관심사 )
	private void addPeerInDB() throws IOException {
		int result = dao.join(getLocalhost(), this.userName);
		if(result > 0) { // 회원정보 DB 저장 성공한 경우
			changePage();	
		}
		else {}	// 회원정보 DB 저장 실패한 경우
	}
	// 관심사 : 페이지 전환하기
	private void changePage() throws IOException {
		moveToLoginPage();
		createPopupPage();
	}
	// 관심사 : 로그인 페이지로 전환하기
	private void moveToLoginPage() throws IOException {
		newPage = new NewPage("/view/login.fxml", stage);
		newPage.createPageOnCurrentStage();
	}
	// 관심사 : 팝업 페이지 띄우기
	private void createPopupPage() throws IOException {
		newPage = new NewPage("/view/popup.fxml", stage);
		Controller controller = newPage.getController();
		controller.setObject("회원가입이 완료되었습니다.");
		newPage.createPageOnNewStage();
	}
	// 관심사 : Peer의 주소생성
	private String getLocalhost() {
		return "localhost:"+ (5500 + (int)(Math.random()*100));
	}
	// 관심사 : userName 유효성 여부 띄우기
	private void setUserNameCheck(String msg) {
		userNameCheck.setText(msg);
		userNameCheck.setVisible(true);
	}

 

회원가입을 담당하는 mainButtonAction() 함수는 기존 코드와 달리, 단 두 줄로 정리되었다. 하나의 함수는 한 가지 관심사(추상화)를 담는다. 한 가지 관심사만 갖기에 각 함수는 짧은 코드를 가진다.

 

여기서  신경써야 할 부분이 하나 더 있다. 바로 '추상화 수준'이다.

 

 

한 가지 추상화 수준

 

@Override
public void mainButtonAction() throws IOException  {
	if(!isEmptyUserName()) doJoinProcess(duplicateCheck());
	else setUserNameCheck("닉네임이 공백입니다.");	
}

 

위 코드는 함수로만 코드가 구성되어있다. 추상화 수준이 높다고 할 수 있다.

 

// 관심사 : Peer의 주소생성
private String getLocalhost() {
	return "localhost:"+ (5500 + (int)(Math.random()*100));
}

 

위 코드는 '+' 같은 연산기호나 (int) 형변환이나 Math.random() 같은 API 사용을 담고 있다. 구체적인 세부 구현사항을 담은 것이다. 이런 함수는 추상화 수준이 낮다고 말할 수있다. 

 

mainButtonAction() 함수는 추상화 수준이 높다. 반면 getLocalhost() 함수는 추상화 수준이 낮다. 

 

이렇듯, 하나의 함수는 하나의 추상화 수준을 가져야 한다.

 

하나의 함수 안에 높은 추상화 수준과 낮은 추상화 수준이 공존해서는 안 된다. 추상화 수준이 다른 코드가 같은 함수 안에 사용되면 혼란스러워진다. 

 

또한,

추상화 수준이 높은 함수는 위에, 추상화 수준이 낮은 함수는 아래에 작성해야 한다. 이를 내려가기 규칙이라 부른다. 

 

 


 

 

이렇듯, 한 개의 추상화, 한 개의 추상화 수준만 지켜도 충분히 클린한 코드를 작성할 수 있다.

반응형