Common sense

Software Engineering

클린코드 & 리팩토링 & 시큐어 코딩

클린코드

클린코드란, 가독성이 높은 코드를 말한다.

가독성을 높이려면 다음과 같이 구현해야 한다.

  • 네이밍이 잘 되어야 함

  • 오류가 없어야 함

  • 중복이 없어야 함

  • 의존성을 최대한 줄여야 함

  • 클래스 혹은 메소드가 한가지 일만 처리해야 함

얼마나 코드가 잘 읽히는 지, 코드가 지저분하지 않고 정리된 코드인지를 나타내는 것이 바로 '클린 코드'

public int AAA(int a, int b){ return a+b; } public int BBB(int a, int b){ return a-b; }

두 가지 문제점이 있다.

public int sum(int a, int b){ return a+b; }
public int sub(int a, int b){ return a-b; }

첫째는 함수 네이밍이다. 다른 사람들이 봐도 무슨 역할을 하는 함수인 지 알 수 있는 이름을 사용해야 한다.

둘째는 함수와 함수 사이의 간격이다. 여러 함수가 존재할 때 간격을 나누지 않으면 시작과 끝을 구분하는 것이 매우 힘들다.

리팩토링

프로그램의 외부 동작은 그대로 둔 채, 내부의 코드를 정리하면서 개선하는 것을 말함

이미 공사가 끝난 집이지만, 더 튼튼하고 멋진 집을 만들기 위해 내부 구조를 개선하는 리모델링 작업

프로젝트가 끝나면, 지저분한 코드를 볼 때 가독성이 떨어지는 부분이 존재한다. 이 부분을 개선시키기 위해 필요한 것이 바로 '리팩토링 기법'

리팩토링 작업은 코드의 가독성을 높이고, 향후 이루어질 유지보수에 큰 도움이 된다.

리팩토링이 필요한 코드는?

  • 중복 코드

  • 긴 메소드

  • 거대한 클래스

  • Switch 문

  • 절차지향으로 구현한 코드

리팩토링의 목적은, 소프트웨어를 더 이해하기 쉽고 수정하기 쉽게 만드는 것

리팩토링은 성능을 최적화시키는 것이 아니다. 코드를 신속하게 개발할 수 있게 만들어주고, 코드 품질을 좋게 만들어준다.

이해하기 쉽고, 수정하기 쉬우면? → 개발 속도가 증가!

리팩토링이 필요한 상황

소프트웨어에 새로운 기능을 추가해야 할 때

명심해야할 것은, 우선 코드가 제대로 돌아가야 한다는 것. 리팩토링은 우선적으로 해야 할 일이 아님을 명심하자

객체지향 특징을 살리려면, switch-case 문을 적게 사용해야 함

(switch문은 오버라이드로 다 바꿔버리자)

리팩토링 예제

클린코드와 리팩토링의 차이?

리팩토링이 더 큰 의미를 가진 것 같다. 클린 코드는 단순히 가독성을 높이기 위한 작업으로 이루어져 있다면, 리팩토링은 클린 코드를 포함한 유지보수를 위한 코드 개선이 이루어진다.

클린코드와 같은 부분은 설계부터 잘 이루어져 있는 것이 중요하고, 리팩토링은 결과물이 나온 이후 수정이나 추가 작업이 진행될 때 개선해나가는 것이 올바른 방향이다.

전문가들이 표현한 '클린코드'

한 가지를 제대로 한다.

단순하고 직접적이다.

특정 목적을 달성하는 방법은 하나만 제공한다.

중복 줄이기, 표현력 높이기, 초반부터 간단한 추상화 고려하기 이 세가지가 비결

코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행하는 것

클린코드란?

코드를 작성하는 의도와 목적이 명확하며, 다른 사람이 쉽게 읽을 수 있어야 함

즉, 가독성이 좋아야 한다.

가독성을 높인다는 것은?

다른 사람이 코드를 봐도, 자유롭게 수정이 가능하고 버그를 찾고 변경된 내용이 어떻게 상호작용하는지 이해하는 시간을 최소화 시키는 것...

클린코드를 만들기 위한 규칙이 있다.

1.네이밍(Naming)

변수, 클래스, 메소드에 의도가 분명한 이름을 사용한다.

int elapsedTimeInDays; int daysSinceCreation; int fileAgeInDays;

잘못된 정보를 전달할 수 있는 이름을 사용하지 않는다.

범용적으로 사용되는 단어 사용X (aix, hp 등)

연속된 숫자나 불용어를 덧붙이는 방식은 피해야함

2.주석달기(Comment)

코드를 읽는 사람이 코드를 작성한 사람만큼 잘 이해할 수 있도록 도와야 함

주석은 반드시 달아야 할 이유가 있는 경우에만 작성하도록 한다.

즉, 코드를 빠르게 유추할 수 있는 내용에는 주석을 사용하지 않는 것이 좋다.

설명을 위한 설명은 달지 않는다.

3.꾸미기(Aesthetics)

보기좋게 배치하고 꾸민다. 보기 좋은 코드가 읽기도 좋다.

규칙적인 들여쓰기와 줄바꿈으로 가독성을 향상시키자

일관성있고 간결한 패턴을 적용해 줄바꿈한다.

메소드를 이용해 불규칙한 중복 코드를 제거한다.

클래스 전체를 하나의 그룹이라고 생각하지 말고, 그 안에서도 여러 그룹으로 나누는 것이 읽기에 좋다.

4.흐름제어 만들기(Making control flow easy to read)

  • 왼쪽에는 변수를, 오른쪽에는 상수를 두고 비교

    `if(length >= 10)

    while(bytes_received < bytest_expected)`

  • 부정이 아닌 긍정을 다루자

    if( a == b ) { // a!=b는 부정 // same } else { // different }

  • if/else를 사용하며, 삼항 연산자는 매우 간단한 경우만 사용

  • do/while 루프는 피하자

5.착한 함수(Function)

함수는 가급적 작게, 한번에 하나의 작업만 수행하도록 작성

온라인 투표로 예를 들어보자

사용자가 추천을 하거나, 이미 선택한 추천을 변경하기 위해 버튼을 누르면 vote_change(old_vote, new_vote) 함수를 호출한다고 가정해보자

var vote_changed = function (old_vote, new_vote) {
	var score = get_score();
	
	if (new_vote !== old_vote) {
		if (new_vote == 'Up') {
			score += (old_vote === 'Down' ? 2 : 1);
		} else if (new_vote == 'Down') {
			score -= (old_vote === 'Up' ? 2 : 1);
		} else if (new_vote == '') {
			score += (old_vote === 'Up' ? -1 : 1);
		}
	}
	set_score(score);
};

총점을 변경해주는 한 가지 역할을 하는 함수같지만, 두가지 일을 하고 있다.

  • old_vote와 new_vote의 상태에 따른 score 계산

  • 총점을 계산

별도로 함수로 분리하여 가독성을 향상시키자

var vote_value = function (vote) {
    if(vote === 'Up') {
        return +1;
    }
    if(vote === 'Down') {
        return -1;
    }
    return 0;
};

var vote_changed = function (old_vote, new_vote) {
    var score = get_score();
    
    score -= vote_value(old_vote); // 이전 값 제거
    score += vote_value(new_vote); // 새로운 값 더함
    set_score(score);
};

훨씬 깔끔한 코드가 되었다!

코드리뷰 & 리팩토링

레거시 코드(테스트가 불가능하거나 어려운 코드)를 클린 코드로 만드는 방법

코드리뷰를 통해 냄새나는 코드를 발견하면, 리팩토링을 통해 점진적으로 개선해나간다.

코드 인스펙션(code inspection)

작성한 개발 소스 코드를 분석하여 개발 표준에 위배되었거나 잘못 작성된 부분을 수정하는 작업

절차 과정

  1. Planning : 계획 수립

  2. Overview : 교육과 역할 정의

  3. Preparation : 인스펙션을 위한 인터뷰, 산출물, 도구 준비

  4. Meeting : 검토 회의로 각자 역할을 맡아 임무 수행

  5. Rework : 발견한 결함을 수정하고 재검토 필요한지 여부 결정

  6. Follow-up : 보고된 결함 및 이슈가 수정되었는지 확인하고 시정조치 이행

리팩토링

냄새나는 코드를 점진적으로 반복 수행되는 과정을 통해 코드를 조금씩 개선해나가는 것

리팩토링 대상

  • 메소드 정리 : 그룹으로 묶을 수 있는 코드, 수식을 메소드로 변경함

  • 객체 간의 기능 이동 : 메소드 기능에 따른 위치 변경, 클래스 기능을 명확히 구분

  • 데이터 구성 : 캡슐화 기법을 적용해 데이터 접근 관리

  • 조건문 단순화 : 조건 논리를 단순하고 명확하게 작성

  • 메소드 호출 단순화 : 메소드 이름이나 목적이 맞지 않을 때 변경

  • 클래스 및 메소드 일반화 : 동일 기능 메소드가 여러개 있으면 수퍼클래스로 이동

리팩토링 진행 방법

아키텍처 관점 시작 → 디자인 패턴 적용 → 단계적으로 하위 기능에 대한 변경으로 진행

의도하지 않은 기능 변경이나 버그 발생 대비해 회귀테스트 진행

이클립스와 같은 IDE 도구로 이용

시큐어 코딩

안전한 소프트웨어를 개발하기 위해, 소스코드 등에 존재할 수 있는 잠재적인 보안약점을 제거하는 것

보안 약점을 노려 발생하는 사고사례들

  • SQL 인젝션 취약점으로 개인유출 사고 발생

  • URL 파라미터 조작 개인정보 노출

  • 무작위 대입공격 기프트카드 정보 유출

SQL 인젝션 예시

  • 안전하지 않은 코드

String query "SELECT * FROM users WHERE userid = '" + userid + "'" + "AND password = '" + password + "'";
Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(query);

적절한 검증 작업이 수행되어야 안전함

입력받는 값의 변수를 $ 대신 #을 사용하면서 바인딩 처리로 시큐어 코딩이 가능하다.

TDD

TDD : 테스트 주도 개발

'테스트가 개발을 이끌어 나간다.'

우리는 보통 개발할 때, 설계(디자인)를 한 이후 코드 개발과 테스트 과정을 거치게 된다.

하지만 TDD는 기존 방법과는 다르게, 테스트케이스를 먼저 작성한 이후에 실제 코드를 개발하는 리팩토링 절차를 밟는다.

`작가가 책을 쓰는 과정에 대해서 생각해보자.

책을 쓰기 전, 목차를 먼저 구성한다. 이후 목차에 맞는 내용을 먼저 구상한 뒤, 초안을 작성하고 고쳐쓰기를 반복한다.

목차 구성 : 테스트 코드 작성 초안 작성 : 코드 개발 고쳐 쓰기 : 코드 수정(리팩토링)`

반복적인 '검토'와 '고쳐쓰기'를 통해 좋은 글이 완성된다. 이런 방법을 소프트웨어에 적용한 것이 TDD!

소프트웨어 또한 반복적인 테스트와 수정을 통해 고품질의 소프트웨어를 탄생시킬 수 있다.

장점

작업과 동시에 테스트를 진행하면서 실시간으로 오류 파악이 가능함 ( 시스템 결함 방지 )

짧은 개발 주기를 통해 고객의 요구사항 빠르게 수용 가능. 피드백이 가능하고 진행 상황 파악이 쉬움

자동화 도구를 이용한 TDD 테스트케이스를 단위 테스트로 사용이 가능함

(자바는 JUnit, C와 C++은 CppUnit 등)

개발자가 기대하는 앱의 동작에 관한 문서를 테스트가 제공해줌또한 이 테스트 케이스는 코드와 함께 업데이트 되므로 문서 작성과 거리가 먼 개발자에게 매우 좋음

단점

기존 개발 프로세스에 테스트케이스 설계가 추가되므로 생산 비용 증가

테스트의 방향성, 프로젝트 성격에 따른 테스트 프레임워크 선택 등 추가로 고려할 부분의 증가

점수 계산 프로그램을 통한 TDD 예제 진행


중간고사, 기말고사, 과제 점수를 통한 성적을 내는 간단한 프로그램을 만들어보자

점수 총합 90점 이상은 A, 80점 이상은 B, 70점 이상은 C, 60점 이상은 D, 나머지는 F다.

TDD 테스트케이스를 먼저 작성한다.

35 + 25 + 25 = 85점이므로 등급이 B가 나와야 한다.

따라서 assertEquals의 인자값을 "B"로 주고, 테스트 결과가 일치하는지 확인하는 과정을 진행해보자

public class GradeTest {
    @Test
    public void scoreResult() {
        
        Score score = new Score(35, 25, 25); // Score 클래스 생성
        SimpleScoreStrategy scores = new SimpleScoreStrategy();
        
        String resultGrade = scores.computeGrade(score); // 점수 계산
        
        assertEquals("B", resultGrade); // 확인
    }
}

현재는 Score 클래스와 computeGrade() 메소드가 구현되지 않은 상태다. (테스트 코드로만 존재)

테스트 코드에 맞춰서 코드 개발을 진행하자

우선 점수를 저장할 Score 클래스를 생성한다

public class Score {
    private int middleScore = 0;
    private int finalScore = 0;
    private int homeworkScore = 0;
    
    public Score(int middleScore, int finalScore, int homeworkScore) {
        this.middleScore = middleScore;
        this.finalScore = finalScore;
        this.homeworkScore = homeworkScore;
    }
    
    public int getMiddleScore(){
        return middleScore;
    }
    
    public int getFinalScore(){
        return finalScore;
    }
    
    public int getHomeworkScore(){
        return homeworkScore;
    }
}

이제 점수 계산을 통해 성적을 뿌려줄 computeGrade() 메소드를 가진 클래스를 만든다.

우선 인터페이스를 구현하자

public interface ScoreStrategy {
    public String computeGrade(Score score);
}

인터페이스를 가져와 오버라이딩한 클래스를 구현한다

public class SimpleScoreStrategy implements ScoreStrategy {
    public String computeGrade(Score score) {
        
        int totalScore = score.getMiddleScore() + score.getFinalScore() + score.getHomeworkScore(); // 점수 총합
        
        String gradeResult = null; // 학점 저장할 String 변수
        
        if(totalScore >= 90) {
            gradeResult = "A";
        } else if(totalScore >= 80) {
            gradeResult = "B";
        } else if(totalScore >= 70) {
            gradeResult = "C";
        } else if(totalScore >= 60) {
            gradeResult = "D";
        } else {
            gradeResult = "F";
        }
        
        return gradeResult;
    }
}

이제 테스트 코드로 돌아가서, 실제로 통과할 정보를 입력해본 뒤 결과를 확인해보자

이때 예외 처리, 중복 제거, 추가 기능을 통한 리팩토링 작업을 통해 완성도 높은 프로젝트를 구현할 수 있도록 노력하자!

통과가 가능한 정보를 넣고 실행하면, 아래와 같이 에러 없이 제대로 실행되는 모습을 볼 수 있다.

굳이 필요하나요?

딱봐도 귀찮아 보인다. 저렇게 확인 안해도 결과물을 알 수 있지 않냐고 반문할 수도 있다.

하지만 예시는 간단하게 보였을 뿐, 실제 실무 프로젝트에서는 다양한 출력 결과물이 필요하고, 원하는 테스트 결과가 나오는 지 확인하는 과정은 필수적인 부분이다.

TDD를 활용하면, 처음 시작하는 단계에서 테스트케이스를 설계하기 위한 초기 비용이 확실히 더 들게 된다. 하지만 개발 과정에 있어서 '초기 비용'보다 '유지보수 비용'이 더 클 수 있다는 것을 명심하자

또한 안전성이 필요한 소프트웨어 프로젝트에서는 개발 초기 단계부터 확실하게 다져놓고 가는 것이 중요하다.

유지보수 비용이 더 크거나 비행기, 기차에 필요한 소프트웨어 등 안전성이 중요한 프로젝트의 경우 현재 실무에서도 TDD를 활용한 개발을 통해 이루어지고 있다.

애자일

애자일을 통한 가장 많이 사용하는 개발 방법론이 '스크럼'

럭비 경기에서 사용되던 용어인데, 반칙으로 인해 경기가 중단됐을 때 쓰는 대형을 말함

즉, 소프트웨어 측면에서 팀이라는 단어가 주는 의미를 적용시키고, 효율적인 성과를 얻기 위한 것

  1. 제품 기능 목록 작성

    개발할 제품에 대한 요구사항 목록 작성

    우선순위가 매겨진, 사용자의 요구사항 목록이라고 말할 수 있음

    개발 중에 수정이 가능하기는 하지만, 일반적으로 한 주기가 끝날 때까지는 제품 기능 목록을 수정하지 않는 것이 원칙

  2. 스프린트 Backlog

    스프린트 각각의 목표에 도달하기 위해 필요한 작업 목록

    • 세부적으로 어떤 것을 구현해야 하는지

    • 작업자

    • 예상 작업 시간

    최종적으로 개발이 어떻게 진행되고 있는지 상황 파악 가능

  3. 스프린트

    작은 단위의 개발 업무를 단기간 내에 전력질주하여 개발한다

    한달동안의 큰 계획을 3~5일 단위로 반복 주기를 정했다면 이것이 스크럼에서 스프린트에 해당함

    • 주기가 회의를 통해 결정되면 (보통 2주 ~ 4주) 목표와 내용이 개발 도중에 바뀌지 않아야 하고, 팀원들 동의 없이 바꿀 수 없는 것이 원칙

  4. 일일 스크럼 회의

    몇가지 규칙이 있다.

    모든 팀원이 참석하여 매일하고, 짧게(15분)하고, 진행 상황 점검한다.

    한사람씩 어제 한 일, 오늘 할 일, 문제점 및 어려운 점을 이야기함

    완료된 세부 작업 항목을 스프린트 현황판에서 업데이트 시킴

  5. 제품완성 및 스프린트 검토 회의

    모든 스프린트 주기가 끝나면, 제품 기능 목록에서 작성한 제품이 완성된다.

    최종 제품이 나오면 고객들 앞에서 시연을 통한 스프린트 검토 회의 진행

    • 고객의 요구사항에 얼마나 부합했는가?

    • 개선점 및 피드백

  6. 스프린트 회고

    스프린트에서 수행한 활동과 개발한 것을 되돌아보며 개선점이나 규칙 및 표준을 잘 준수했는지 검토

    팀의 단점보다는 강점과 장점을 찾아 더 극대화하는데 초점을 둔다

스크럼 장점


  • 스프린트마다 생산되는 실행 가능한 제품을 통해 사용자와 의견을 나눌 수 있음

  • 회의를 통해 팀원들간 신속한 협조와 조율이 가능

  • 자신의 일정을 직접 발표함으로써 업무 집중 환경 조성

  • 프로젝트 진행 현황을 통해 신속하게 목표와 결과 추정이 가능하며 변화 시도가 용이함

스크럼 단점


  • 추가 작업 시간이 필요함 (스프린트마다 테스트 제품을 만들어야하기 때문)

  • 15분이라는 회의 시간을 지키기 힘듬 ( 시간이 초과되면 그만큼 작업 시간이 줄어듬)

  • 스크럼은 프로젝트 관리에 무게중심을 두기 때문에 프로세스 품질 평가에는 미약함

객체 지향 프로그래밍

보통 OOP라고 많이 부른다. 객체지향은 수 없이 많이 들어왔지만, 이게 뭔지 설명해달라고 하면 어디서부터 해야할 지 막막해진다.. 개념을 잡아보자

객체지향 패러다임이 나오기 이전의 패러다임들부터 간단하게 살펴보자.

패러다임의 발전 과정을 보면 점점 개발자들이 편하게 개발할 수 있도록 개선되는 방식으로 나아가고 있는 걸 확인할 수 있다.

가장 먼저 순차적, 비구조적 프로그래밍이 있다. 말 그대로 순차적으로 코딩해나가는 것!

필요한 게 있으면 계속 순서대로 추가해가며 구현하는 방식이다. 직관적일 것처럼 생각되지만, 점점 규모가 커지게 되면 어떻게 될까?

이런 비구조적 프로그래밍에서는 goto문을 활용한다. 만약 이전에 작성했던 코드가 다시 필요하면 그 곳으로 이동하기 위한 것이다. 점점 규모가 커지면 goto문을 무분별하게 사용하게 되고, 마치 실뜨기를 하는 것처럼 베베 꼬이게 된다. (코드 안에서 위로 갔다가 아래로 갔다가..뒤죽박죽) 나중에 코드가 어떻게 연결되어 있는지 확인조차 하지 못하게 될 문제점이 존재한다.

이러면, 코딩보다 흐름을 이해하는 데 시간을 다 소비할 가능성이 크다

오늘날 수업을 듣거나 공부하면서 goto문은 사용하지 않는게 좋다!라는 말을 분명 들어봤을 것이다. goto문은 장기적으로 봤을 때 크게 도움이 되지 않는 구현 방식이기 때문에 그런 것이었다.

이런 문제점을 해결하기 위해 탄생한 것이 바로 절차적, 구조적 프로그래밍이다. 이건 대부분 많이 들어본 패러다임일 것이다.

반복될 가능성이 있는 것들을 재사용이 가능한 함수(프로시저)로 만들어 사용하는 프로그래밍 방식이다.

여기서 보통 절차라는 의미는 함수(프로시저)를 뜻하고, 구조는 모듈을 뜻한다. 모듈이 함수보다 더 작은 의미이긴 하지만, 요즘은 큰 틀로 같은 의미로 쓰이고 있다.

프로시저는 뭔가요?

반환값(리턴)이 따로 존재하지 않는 함수를 뜻한다. 예를 들면, printf와 같은 함수는 반환값을 얻기 위한 것보단, 화면에 출력하는 용도로 쓰이는 함수다. 이와 같은 함수를 프로시저로 부른다.

(정확히 말하면 printf는 int형을 리턴해주기는 함. 하지만 목적 자체는 프로시저에 가까움)

하지만 이런 패러다임도 문제점이 존재한다. 바로 너무 추상적이라는 것..

실제로 사용되는 프로그램들은 추상적이지만은 않다. 함수는 논리적 단위로 표현되지만, 실제 데이터에 해당하는 변수나 상수 값들은 물리적 요소로 되어있기 때문이다.

도서관리 프로그램이 있다고 가정해보자.

책에 해당하는 자료형(필드)를 구현해야 한다. 또한 책과 관련된 함수를 구현해야 한다. 구조적인 프로그래밍에서는 이들을 따로 만들어야 한다. 결국 많은 데이터를 만들어야 할 때, 구분하기 힘들고 비효율적으로 코딩할 가능성이 높아진다.

책에 대한 자료형, 책에 대한 함수가 물리적으론 같이 있을 수 있지만 (같은 위치에 기록)

논리적으로는 함께할 수 없는 구조가 바로 구조적 프로그래밍

따라서, 이를 한번에 묶기 위한 패러다임이 탄생한다.

바로 객체지향 프로그래밍이다.

우리가 vo를 만들 때와 같은 형태다. 클래스마다 필요한 필드를 선언하고, getter와 setter로 구성된 모습으로 해결한다. 바로 특정한 개념의 함수와 자료형을 함께 묶어서 관리하기 위해 탄생한 것!

가장 중요한 점은, 객체 내부에 자료형(필드)와 함수(메소드)가 같이 존재하는 것이다.

이제 도서관리 프로그램을 만들 때, 해당하는 책의 제목, 저자, 페이지와 같은 자료형과 읽기, 예약하기 등 메소드를 '책'이라는 객체에 한번에 묶어서 저장하는 것이 가능해졌다.

이처럼 가능한 모든 물리적, 논리적 요소를 객체로 만드려는 것이 객체지향 프로그래밍이라고 말할 수 있다.

객체지향으로 구현하게 되면, 객체 간의 독립성이 생기고 중복코드의 양이 줄어드는 장점이 있다. 또한 독립성이 확립되면 유지보수에도 도움이 될 것이다.

특징

객체지향의 패러다임이 생겨나면서 크게 4가지 특징을 갖추게 되었다.

이 4가지 특성을 잘 이해하고 구현해야 객체를 통한 효율적인 구현이 가능해진다.

  1. 추상화(Abstraction)

    필요로 하는 속성이나 행동을 추출하는 작업

    추상적인 개념에 의존하여 설계해야 유연함을 갖출 수 있다.

    즉, 세부적인 사물들의 공통적인 특징을 파악한 후 하나의 집합으로 만들어내는 것이 추상화다

    `ex. 아우디, BMW, 벤츠는 모두 '자동차'라는 공통점이 있다.

    자동차라는 추상화 집합을 만들어두고, 자동차들이 가진 공통적인 특징들을 만들어 활용한다.`

    '왜 필요하죠?'

    예를 들면, '현대'와 같은 다른 자동차 브랜드가 추가될 수도 있다. 이때 추상화로 구현해두면 다른 곳의 코드는 수정할 필요 없이 추가로 만들 부분만 새로 생성해주면 된다.

  2. 캡슐화(Encapsulation)

    낮은 결합도를 유지할 수 있도록 설계하는 것

    쉽게 말하면, 한 곳에서 변화가 일어나도 다른 곳에 미치는 영향을 최소화 시키는 것을 말한다.

    (객체가 내부적으로 기능을 어떻게 구현하는지 감추는 것!)

    결합도가 낮도록 만들어야 하는 이유가 무엇일까? 결합도(coupling)란, 어떤 기능을 실행할 때 다른 클래스나 모듈에 얼마나 의존적인가를 나타내는 말이다.

    즉, 독립적으로 만들어진 객체들 간의 의존도가 최대한 낮게 만드는 것이 중요하다. 객체들 간의 의존도가 높아지면 굳이 객체 지향으로 설계하는 의미가 없어진다.

    우리는 소프트웨어 공학에서 객체 안의 모듈 간의 요소가 밀접한 관련이 있는 것으로 구성하여 응집도를 높이고 결합도를 줄여야 요구사항 변경에 대처하는 좋은 설계 방법이라고 배운다.

    이것이 바로 캡슐화와 크게 연관된 부분이라고 할 수 있다.

    그렇다면, 캡슐화는 어떻게 높은 응집도와 낮은 결합도를 갖게 할까?

    바로 정보 은닉을 활용한다.

    외부에서 접근할 필요가 없는 것들은 private으로 접근하지 못하도록 제한을 두는 것이다.

    (객체안의 필드를 선언할 때 private으로 선언하라는 말이 바로 이 때문!!)

  3. 상속

    일반화 관계(Generalization)라고도 하며, 여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정

    일반화(상속)은 또 다른 캡슐화다.

    자식 클래스를 외부로부터 은닉하는 캡슐화의 일종이라고 말할 수 있다.

    아까 자동차를 통해 예를 들어 추상화를 설명했었다. 여기에 추가로 대리 운전을 하는 사람 클래스가 있다고 생각해보자. 이때, 자동차의 자식 클래스에 해당하는 벤츠, BMW, 아우디 등은 캡슐화를 통해 은닉해둔 상태다.

    사람 클래스의 관점으로는, 구체적인 자동차의 종류가 숨겨져 있는 상태다. 대리 운전자 입장에서는 자동차의 종류가 어떤 것인지는 운전하는데 크게 중요하지 않다.

    새로운 자동차들이 추가된다고 해도, 사람 클래스는 영향을 받지 않는 것이 중요하다. 그러므로 캡슐화를 통해 사람 클래스 입장에서는 확인할 수 없도록 구현하는 것이다.

    이처럼, 상속 관계에서는 단순히 하나의 클래스 안에서 속성 및 연산들의 캡슐화에 한정되지 않는다. 즉, 자식 클래스 자체를 캡슐화하여 '사람 클래스'와 같은 외부에 은닉하는 것으로 확장되는 것이다.

    이처럼 자식 클래스를 캡슐화해두면, 외부에선 이러한 클래스들에 영향을 받지 않고 개발을 이어갈 수 있는 장점이 있다.

    상속 재사용의 단점

    상속을 통한 재사용을 할 때 나타나는 단점도 존재한다.

    1. 상위 클래스(부모 클래스)의 변경이 어려워진다.

    부모 클래스에 의존하는 자식 클래스가 많을 때, 부모 클래스의 변경이 필요하다면?

    이를 의존하는 자식 클래스들이 영향을 받게 된다.

    1. 불필요한 클래스가 증가할 수 있다.

    유사기능 확장시, 필요 이상의 불필요한 클래스를 만들어야 하는 상황이 발생할 수 있다.

    1. 상속이 잘못 사용될 수 있다.

    같은 종류가 아닌 클래스의 구현을 재사용하기 위해 상속을 받게 되면, 문제가 발생할 수 있다. 상속 받는 클래스가 부모 클래스와 IS-A 관계가 아닐 때 이에 해당한다.

    해결책은?

    객체 조립(Composition), 컴포지션이라고 부르기도 한다.

    객체 조립은, 필드에서 다른 객체를 참조하는 방식으로 구현된다.

    상속에 비해 비교적 런타임 구조가 복잡해지고, 구현이 어려운 단점이 존재하지만 변경 시 유연함을 확보하는데 장점이 매우 크다.

    따라서 같은 종류가 아닌 클래스를 상속하고 싶을 때는 객체 조립을 우선적으로 적용하는 것이 좋다.

    그럼 상속은 언제 사용?

    • IS-A 관계가 성립할 때

    • 재사용 관점이 아닌, 기능의 확장 관점일 때

  4. 다형성(Polymorphism)

    서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력

    객체 지향의 핵심과도 같은 부분이다.

    다형성은, 상속과 함께 활용할 때 큰 힘을 발휘한다. 이와 같은 구현은 코드를 간결하게 해주고, 유연함을 갖추게 해준다.

    즉, 부모 클래스의 메소드를 자식 클래스가 오버라이딩해서 자신의 역할에 맞게 활용하는 것이 다형성이다.

    이처럼 다형성을 사용하면, 구체적으로 현재 어떤 클래스 객체가 참조되는 지는 무관하게 프로그래밍하는 것이 가능하다.

    상속 관계에 있으면, 새로운 자식 클래스가 추가되어도 부모 클래스의 함수를 참조해오면 되기 때문에 다른 클래스는 영향을 받지 않게 된다.

객체 지향 설계 과정

  • 제공해야 할 기능을 찾고 세분화한다. 그리고 그 기능을 알맞은 객체에 할당한다.

  • 기능을 구현하는데 필요한 데이터를 객체에 추가한다.

  • 그 데이터를 이용하는 기능을 넣는다.

  • 기능은 최대한 캡슐화하여 구현한다.

  • 객체 간에 어떻게 메소드 요청을 주고받을 지 결정한다.

객체 지향 설계 원칙

SOLID라고 부르는 5가지 설계 원칙이 존재한다.

  1. SRP(Single Responsibility) - 단일 책임 원칙

    클래스는 단 한 개의 책임을 가져야 한다.

    클래스를 변경하는 이유는 단 한개여야 한다.

    이를 지키지 않으면, 한 책임의 변경에 의해 다른 책임과 관련된 코드에 영향이 갈 수 있다.

  2. OCP(Open-Closed) - 개방-폐쇄 원칙

    확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

    기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 수정하지 않는다.

    이를 지키지 않으면, instanceof와 같은 연산자를 사용하거나 다운 캐스팅이 일어난다.

  3. LSP(Liskov Substitution) - 리스코프 치환 원칙

    상위 타입의 객체를 하위 타입의 객체로 치환해도, 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

    상속 관계가 아닌 클래스들을 상속 관계로 설정하면, 이 원칙이 위배된다.

  4. ISP(Interface Segregation) - 인터페이스 분리 원칙

    인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.

    각 클라이언트가 필요로 하는 인터페이스들을 분리함으로써, 각 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않도록 만들어야 한다.

  5. DIP(Dependency Inversion) - 의존 역전 원칙

    고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.

    저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.

    즉, 저수준 모듈이 변경돼도 고수준 모듈은 변경할 필요가 없는 것이다.

함수형 프로그래밍

순수 함수를 조합하고 공유 상태, 변경 가능한 데이터 및 부작용을 피해 소프트웨어를 만드는 프로세스

https://user-images.githubusercontent.com/6733004/46247710-5ce5fb00-c44a-11e8-9400-16dd44626820.png

'선언형' 프로그래밍으로, 애플리케이션의 상태는 순수 함수를 통해 전달된다.

애플리케이션의 상태가 일반적으로 공유되고 객체의 메서드와 함께 배치되는 OOP와는 대조되는 프로그래밍 방식

  • 명령형 프로그래밍(절차지향, 객체지향)

    상태와 상태를 변경시키는 관점에서 연산을 설명하는 방식

    알고리즘을 명시하고, 목표는 명시하지 않음

  • 선언형 프로그래밍

    How보다는 What을 설명하는 방식 (어떻게보단 무엇을)

    알고리즘을 명시하지 않고 목표만 명시함

명령형 프로그래밍은 어떻게 할지 표현하고, 선언형 프로그래밍은 무엇을 할 건지 표현한다.

함수형 코드는 명령형 프로그래밍이나 OOP 코드보다 더 간결하고 예측가능하여 테스트하는 것이 쉽다.

(하지만 익숙치 않으면 더 복잡해보이고 이해하기 어려움)

함수형 프로그래밍은 프로그래밍 언어나 방식을 배우는 것이 아닌, 함수로 프로그래밍하는 사고를 배우는 것이다.

기존의 사고방식을 전환하여 프로그래밍을 더 유연하게 문제해결 하도록 접근하는 것

함수형 프로그래밍의 의미를 파악하기 전 꼭 알아야 할 것들

  • 순수 함수 (Pure functions)

    입출력이 순수해야함 : 반드시 하나 이상의 인자를 받고, 받은 인자를 처리해 반드시 결과물을 돌려줘야 함. 인자 외 다른 변수 사용 금지

  • 합성 함수 (Function composition)

  • 공유상태 피하기 (Avoid shared state)

  • 상태변화 피하기 (Avoid mutating state)

  • 부작용 피하기 (Avoid side effects)

    프로그래머가 바꾸고자 하는 변수 외에는 변경되면 안됨. 원본 데이터는 절대 불변!

대표적인 자바스크립트 함수형 프로그래밍 함수 : map, filter, reduce

함수형 프로그래밍 예시

var arr = [1, 2, 3, 4, 5];

var map = arr.map(function(x) { return x * 2; }); // [2, 4, 6, 8, 10]

arr을 넣어서 map을 얻었음. arr을 사용했지만 값은 변하지 않았고 map이라는 결과를 내고 어떠한 부작용도 낳지 않음

이런 것이 바로 함수형 프로그래밍의 순수함수라고 말한다.

var arr = [1, 2, 3, 4, 5];

var condition = function(x) { return x % 2 === 0; }

var ex = function(array) { return array.filter(condition); };

ex(arr); // [2, 4]

이는 순수함수가 아니다. 이유는 ex 메소드에서 인자가 아닌 condition을 사용했기 때문.

순수함수로 고치면 아래와 같다.

var ex = function(array, cond) { return array.filter(cond); }; ex(arr, condition);

순수함수로 만들면, 에러를 추적하는 것이 쉬워진다. 인자에 문제가 있거나 함수 내부에 문제가 있거나 둘 중 하나일 수 밖에 없기 때문이다.

Java에서의 함수형 프로그래밍

Java 8이 릴리즈되면서, Java에서도 함수형 프로그래밍이 가능해졌다.

함수형 프로그래밍 : 부수효과를 없애고 순수 함수를 만들어 모듈화 수준을 높이는 프로그래밍 패러다임

부수효과 : 주어진 값 이외의 외부 변수 및 프로그래밍 실행에 영향을 끼치지 않아야 된다는 의미

최대한 순수함수를 지향하고, 숨겨진 입출력을 최대한 제거하여 코드를 순수한 입출력 관계로 사용하는 것이 함수형 프로그래밍의 목적이다.

Java의 객체 지향은 명령형 프로그래밍이고, 함수형은 선언형 프로그래밍이다.

둘의 차이는 문제해결의 관점

여태까지 우리는 Java에서 객체지향 프로그래밍을 할 때 '데이터를 어떻게 처리할 지에 대해 명령을 통해 해결'했다.

함수형 프로그래밍은 선언적 함수를 통해 '무엇을 풀어나가야할지 결정'하는 것이다.

Java에서 활용할 수 있는 함수형 프로그래밍

  • 람다식

  • stream api

  • 함수형 인터페이스

Java 8에는 Stream API가 추가되었다.

import java.util.Arrays; 
import java.util.List;
public class stream {
    public static void main(String[] args) {
    	List<String> myList = Arrays.asList("a", "b", "c", "d", "e");
    
        // 기존방식
        for(int i=0; i<myList.size(); i++){
            String s = myList.get(i);
            if(s.startsWith("c")){
                System.out.println(s.toUpperCase());
            }
        }
    
        // stream API를 이용한 방식
        myList.stream()
              .filter(s -> s.startsWith("c"))
              .map(String::toUpperCase)
              .forEach(System.out::println);
    
    }
}

뭐가 다른건지 크게 와닿지 않을 수 있지만, 중요한건 프로그래밍의 패러다임 변화라는 것이다.

단순히 함수를 선언해서 데이터를 내가 원하는 방향으로 처리해나가는 함수형 프로그래밍 방식을 볼 수 있다. 한눈에 보더라도 함수형 프로그래밍은 내가 무엇을 구현했는지 명확히 알 수 있다. (무슨 함수인지 사전학습이 필요한 점이 있음)

서드 파티란 ?

간혹 써드 파티라는 말을 종종 볼 수 있다. 경제 용어가 IT에서 쓰이는 부분이다.

3rd party

하드웨어 생산자와 소프트웨어 개발자의 관계를 나타낼 때 사용한다.

그 중에서 서드파티는, 프로그래밍을 도와주는 라이브러리를 만드는 외부 생산자를 뜻한다.

ex) 게임제조사와 소비자를 연결해주는 게임회사(퍼플리싱) 스마일게이트와 같은 회사

개발자 측면으로 보면?

  • 하드웨어 생산자가 '직접' 소프트웨어를 개발하는 경우 : 퍼스트 파티 개발자

  • 하드웨어 생산자인 기업과 자사간의 관계(또는 하청업체)에 속한 소프트웨어 개발자 : 세컨드 파티 개발자

  • 아무 관련없는 제3자 소프트웨어 개발자 : 서드 파티 개발자

주로 편한 개발을 위해 플러그인이나 라이브러리 혹은 프레임워크를 사용하는데, 이처럼 제 3자로 중간다리 역할로 도움을 주는 것이 서드 파티로 볼 수 있고, 이런 것을 만드는 개발자가 서드 파티 개발자다.

MSA

MSA는 소프트웨어 개발 기법 중 하나로, 어플리케이션 단위를 '목적'으로 나누는 것이 핵심

Monolithic vs MSA

MSA가 도입되기 전, Monolithic 아키텍처 방식으로 개발이 이루어졌다. Monolithic의 사전적 정의에 맞게 '한 덩어리'에 해당하는 구조로 이루어져 있다. 모든 기능을 하나의 어플리케이션에서 비즈니스 로직을 구성해 운영한다. 따라서 개발을 하거나 환경설정에 있어서 간단한 장점이 있어 작은 사이즈의 프로젝트에서는 유리하지만, 시스템이 점점 확장되거나 큰 프로젝트에서는 단점들이 존재한다.

  • 빌드/테스트 시간의 증가 : 하나를 수정해도 시스템 전체를 빌드해야 함. 즉, 유지보수가 힘들다

  • 작은 문제가 시스템 전체에 문제를 일으킴 : 만약 하나의 서비스 부분에 트래픽 문제로 서버가 다운되면, 모든 서비스 이용이 불가능할 것이다.

  • 확장성에 불리 : 서비스 마다 이용률이 다를 수 있다. 하나의 서비스를 확장하기 위해 전체 프로젝트를 확장해야 한다.

MSA는 좀 더 세분화 시킨 아키텍처라고 말할 수 있다. 한꺼번에 비즈니스 로직을 구성하던 Monolithic 방식과는 다르게 기능(목적)별로 컴포넌트를 나누고 조합할 수 있도록 구축한다.

MSA에서 각 컴포넌트는 API를 통해 다른 서비스와 통신을 하는데, 모든 서비스는 각각 독립된 서버로 운영하고 배포하기 때문에 서로 의존성이 없다. 하나의 서비스에 문제가 생겨도 다른 서비스에는 영향을 끼치지 않으며, 서비스 별로 부분적인 확장이 가능한 장점이 있다.

즉, 서비스 별로 개발팀이 꾸려지면 다른 팀과 의존없이 팀 내에서 피드백을 빠르게 할 수 있고, 비교적 유연하게 운영이 가능할 것이다.

좋은 점만 있지는 않다. MSA는 서비스 별로 호출할 때 API로 통신하므로 속도가 느리다. 그리고 서비스 별로 통신에 맞는 데이터로 맞추는 과정이 필요하기도 하다. Monolithic 방식은 하나의 프로세스 내에서 진행되기 때문에 속도 면에서는 MSA보다 훨씬 빠를 것이다. 또한, MSA는 DB 또한 개별적으로 운영되기 때문에 트랜잭션으로 묶기 힘든 점도 있다.

따라서, 서비스별로 분리를 하면서 얻을 수 있는 장점도 있지만, 그만큼 체계적으로 준비돼 있지 않으면 MSA로 인해 오히려 프로젝트 성능이 떨어질 수도 있다는 점을 알고있어야 한다. 정답이 정해져 있는 것이 아니라, 프로젝트 목적, 현재 상황에 맞는 아키텍처 방식이 무엇인지 설계할 때부터 잘 고민해서 선택하자.

DDD

reference

CQRS

CQRS는 정보를 업데이트 할때와 조회할 때 다른 모델을 사용하는 것이 가장 핵심이다.

좋은 코드란?

‘좋은 코드란?‘이라고 구글링해보면 많은 검색 결과가 나온다. 나도 그렇고 다들 궁금했던듯하다. ‘좋은 코드’란 녀석은 정체도, 실체도 없이 이 세상에 떠돌고 있다. 모두가 ‘좋은 코드’의 기준이 조금씩 다르고 각각의 경험을 기반으로 좋은 코드를 정의하고 있다. 세간에 좋은 코드의 정의는 정말 많다.

  • 읽기 쉬운 코드

  • 중복이 없는 코드

  • 테스트가 용이한 코드

RESTful API란 ?

우선, 위키백과의 정의를 요약해보자면 다음과 같다.

월드 와이드 웹(World Wide Web a.k.a WWW)과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식으로 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반에 대한 패턴

REST란, REpresentational State Transfer 의 약자이다. 여기에 ~ful 이라는 형용사형 어미를 붙여 ~한 API 라는 표현으로 사용된다. 즉, REST 의 기본 원칙을 성실히 지킨 서비스 디자인은 'RESTful'하다라고 표현할 수 있다.

REST가 디자인 패턴이다, 아키텍처다 많은 이야기가 존재하는데, 하나의 아키텍처로 볼 수 있다. 좀 더 정확한 표현으로 말하자면, REST 는 Resource Oriented Architecture 이다. API 설계의 중심에 자원(Resource)이 있고 HTTP Method 를 통해 자원을 처리하도록 설계하는 것이다.

REST 6 가지 원칙

  • Uniform Interface

  • Stateless

  • Caching

  • Client-Server

  • Hierarchical system

  • Code on demandcf) 보다 자세한 내용에 대해서는 Reference 를 참고해주세요.

RESTful 하게 API 를 디자인 한다는 것은 무엇을 의미하는가.(요약)

  1. 리소스행위 를 명시적이고 직관적으로 분리한다.

  • 리소스는 URI로 표현되는데 리소스가 가리키는 것은 명사로 표현되어야 한다.

  • 행위는 HTTP Method로 표현하고, GET(조회), POST(생성), PUT(기존 entity 전체 수정), PATCH(기존 entity 일부 수정), DELETE(삭제)을 분명한 목적으로 사용한다.

  1. Message 는 Header 와 Body 를 명확하게 분리해서 사용한다.

  • Entity 에 대한 내용은 body 에 담는다.

  • 애플리케이션 서버가 행동할 판단의 근거가 되는 컨트롤 정보인 API 버전 정보, 응답받고자 하는 MIME 타입 등은 header 에 담는다.

  • header 와 body 는 http header 와 http body 로 나눌 수도 있고, http body 에 들어가는 json 구조로 분리할 수도 있다.

  1. API 버전을 관리한다.

  • 환경은 항상 변하기 때문에 API 의 signature 가 변경될 수도 있음에 유의하자.

  • 특정 API 를 변경할 때는 반드시 하위호환성을 보장해야 한다.

  1. 서버와 클라이언트가 같은 방식을 사용해서 요청하도록 한다.

  • 브라우저는 form-data 형식의 submit 으로 보내고 서버에서는 json 형태로 보내는 식의 분리보다는 json 으로 보내든, 둘 다 form-data 형식으로 보내든 하나로 통일한다.

  • 다른 말로 표현하자면 URI 가 플랫폼 중립적이어야 한다.

어떠한 장점이 존재하는가?

  1. Open API 를 제공하기 쉽다

  2. 멀티플랫폼 지원 및 연동이 용이하다.

  3. 원하는 타입으로 데이터를 주고 받을 수 있다.

  4. 기존 웹 인프라(HTTP)를 그대로 사용할 수 있다.

단점은 뭐가 있을까?

  1. 사용할 수 있는 메소드가 4 가지 밖에 없다.

  2. 분산환경에는 부적합하다.

  3. HTTP 통신 모델에 대해서만 지원한다.

위 내용은 간단히 요약된 내용이므로 보다 자세한 내용은 다음 Reference 를 참고하시면 됩니다 :)

Reference

MVC pattern ?

reference

Git & Github ?

Git 이란 VCS(Version Control System)에 대해서 기본적인 이해를 요구하고 있다.

Git 을 사용하기 위한 각종 전략(strategy)들이 존재한다. 해당 전략들에 대한 이해를 기반으로 Git 을 사용해야 하기 때문에 면접에서 자주 물어본다. 주로 사용되는 strategy 중심으로 질문이 들어오며 유명한 세 가지를 비교한 글을 첨부한다.

많은 회사들이 GitHub 을 기반으로 협업을 하게 되는데, (BitBucket 이라는 훌륭한 도구도 존재합니다.) GitHub 에서 어떤 일을 할 수 있는지, 어떻게 GitHub Repository 에 기여를 하는지 정리한 글을 첨부한다.

Last updated