토비의 스프링 chaper 4 요약

토비의 스프링 Chpater 4

어려운 토비의 스프링 3장이 너무 힘들었지만 4장도 하나하나 천천히 읽어나가야 겠다. 4장에서는 놓치기 쉬운 db관련 예외처리를 다루고 있다.

4장

난감 예외처리

흔히 할 수 있는 예외처리

try {
    // ...
} catch (SQLException e) {
    // do noting..
}

try-catch로 감싸면 컴파일 에러도 안나고 문제 없이 동작하니까 그냥 예외를 잡는 코드만 작성하기 쉽다.. 나도 이제까지 그랬고, 하더라도 스택 프린트하는것 밖에 안했으니 ..ㅠ

여기선 이 문제를 아주 강력하게 집어주고 있다 잡아내는 것까지 했으면 무시하지 말고 처리를 하라고 말이다.

catch(Exception e) {
    e.printStackTrace();
    System.exit(1);
}

이렇게라도 하는게 백배 낫다고 한다. 자숙 많이 해야겠다.

또한 책에서는 무책임하게 예외를 메소드 밖으로, 던져버리는 throw 를 비판한다. 심지어 예외가 발생할 만한 메소드에는 각각의 정확안 예외명을 작성하기도 귀찮아 throw Exception 으로 뭉퉁그려 던지는 걸 극혐하고 있다.

예외의 종류와 특징

  1. Error java.lang.Error 서브크래스로 시스템에 뭔가 비정상적인 상황이 발생했을 경우 ex) OutOfMemoryError, ThreadDeath

  2. Exception과 체크 예외 java.lang.Exception 클래스, 서브클래스로 정의되는 예외로 코드작업중 예외상황 발생할 경우 ex) IOException, SQLException

  3. RuntimeException과 언체크/런타임 예외 java.lang.RuntimeException 클래스를 상속한 예외로 명시적으로 잡아주지 않아도 될만한 예외들 ex) NullPointerException, IllegalArgumentException

예외처리 방법

예외 복구

예외로 인해 기본 작업 흐름이 불가능하면 다른 작업 흐름으로 자연스럽게 유도해주는 것. 그렇다면 예외인 상황은 다시 정상으로 돌어오고 복구 했다고 볼 수 있다.

예외처리 회피

예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것. throws 문으로 선언해서 예외가 발생하면 던져버리기. 아니면 catch로 우선 잡고 로그 출력하고 던지기.

public void someMethod() throws SomeException {
    try {
        // ..
    } catch (SomeException e) {
        printLog();
        throw e;
    }
}

이때 주의할건 내가 처리하는게 아니라 다른 쪽에서 예외를 처리하게끔 하는게 타당하고 분명한 이유가 있어야 한다.

예외전환

예외 전환도 예외를 복구해서 정상으로 만들 수 없기에 메소드 밖으로 던져버리는 방식인데 예외처리 회피랑은 다른 점은 발생한 예외를 좀 더 구체적인 예외로 명시해서 던진다는 것이다.

예를 들면 회원 가입시 아이디가 중복되면 sql 예외를 발생시키는데 이대로 던지면 서비스 계층에선 왜 때문에 예외가 발생했는지 모른다.

그러니까 catch로 우선 잡고 분석해보고 아 사용자 중복이구나 라고 판단한다. 그런 다음 좀 더 구체적인 예외를 메소드 밖으로 던지는 것이다.

그리고 보통 구체적인 예외를 던질 때 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다.

catch (SQLException e {
    throw SomeException(e);
}

예외처리 전략

런타임 예외의 보편화

체크 예외의 문제는 복구할 가능성이 조금이라도 있다고 보고 개발자로 하여금 catch블록이나 throws 선언을 강제하는 것이다.

하지만 자바 엔터프라이즈 서버환경은 독립적인 애플리케이션과는 다르게 수 많은 요청을 감당할 만한 예외처리를 할 수 없다.정확하겐 예외가 발행했을 때 이걸 복구 할 수 있는게 안된다. 그래서 예외가 발생한 요청을 중단하고 관리자나 사용자에게 통보하는게 더 낫다.

요즘은 항상 복구할 수 있는 예외가 아니라면 .. 일단 언체크로 만들고 런타임 예외로 포장해서 던저버리는게 보편화 됐다. 정말 이해하기 어렵다..

또하나 throws에 복잡하고 여러 예외상황을 나열하지 않고 구체적인 런타임 에러로 전환해서 던지는 편이 낫다.

애플리케이션 예외

시스템 또는 외부의 예외상황이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고, catch로 잡아서 무언가 조치를 취하려는 예외도 있다.

정상적으로 동작할 때의 로직을 예쁘게 작성한다. 하지만 여기엔 예외가 발생할 가능성이 있다. 그러고 예외가 발생했을 때 필요한 로직을 catch로 따로 작성한다. 의도적으로 발생할 수 있는 가능성을 열어두고 발생하면 그에 맞는 다른 로직을 실행하는 것이다.

시스템 예외라면 애플리케이션 레벨에서 복구할 방법이 없다!

이 처럼 복구 할 수있는 방법이 없는데 예외를 어떻게 다루냔 말이다. 그럼에도 불구하고 예외를 처리하라고 하니 뭐 어쩌라고.. 답은 예외처리 전략을 사용해야 한다. 필요도 없는 무의미한 throws 선언을 나두지 말고 가능한 빨리 언체크/런타임 예외로 전환해야 한다는 소리이다!

예를 들면 jdbcTemplate이 처리 불가능한 SQLException을 런타임 예외인 DataAccessException 으로 포장해서 던저 준다. 요런 방식을 적용하란 소리다.

예외전환

사용이유

  1. 런타임 예외로 포장해서 불필요한 catch/throw를 줄여주는 것
  2. 좀 더 의미있는 예외로 바꿔 주는 것

JDBC

jdbc로 인해db 프로그램 개발방법을 학습하는데 부담이 확실히 줄었다. 각각의 db의 구체적인 동작 방식을 몰라도 jdbc를 통하면 쉽게 사용이 가능하기 때문이다. 하지만 db를 자유롭게 변경해서 사용할 수 있는 유연한 코드를 보장해 주진 못한다.

jdbc의 한계는 2가지가 있다. SQL은 대체적으로 표준화 되어 있지만 각 DB들 마다 최적화등을 위한 비표준 SQL이 존재한다. 프로그램의 성능을 위해 불가피하게 비표준 SQL이 dao에 적힐 것이고, 특정 db에 종속될 것이다. db 변경 가능성을 염두한다면 작지 않은 걸림돌이 된다.

다른 하나는 SQLException의 에러정보가 모두 db마다 다르다는 것이다. jdbc는 데이터 처리 도중 다양한 예외 발생시 모두 SQLException으로 던져 버린다… 무책임하게도. 그래서 예외 원인을 알아보려면 예외 정보 를 확인해야 하는데 이게 db마다 모두 다른 예외 코드로 나타난다.

에러코드 문제

위에서 엄청난 걱정을 하게 만들었지만 jdbcTemplate 을 이용하면 DB관련 예외는 거의 신경쓰지 않아도 된다.

이미 DB별로 예외코드를 예외 클래스랑 Mapping을 시켜놨기 때문..

DAO 와 DataAccessException

SQLExeption을 DataAccessException으로 전환하는 이유가 또 있다. 런타임으로 바꾸면서 얻는 이점이 굉장히 많은 것 같다.

public interface UserDao {
    public void add(User user) // 이게 가능할까?
}

userDao 구현체에서 add 를 구현할 때 jdbc를 사용할 텐데 거기에선 분명 SqlExeption을 처리해야 한다. 그럼 interface에서 선언하지 않은 throw가 생길테고 유연함이 목적인 UserDao에서 종속성이 발생한다.

이때 SqlException을 런타임 예외로 포장해주면 이 문제는 해결된다. 하지만 data access 기술이 달라지면 > 같은 상황인데 다른 예외가 발생하니까.. 이걸 그냥 모두 런타임으로 바꾸기만 한다고 근본적인 해결은 안된다.


출처 :

  • 이 글은 이일민 지음의 ‘토비의 스프링 3.1 vol.1’ 을 읽고 작성되었습니다.
토비의 스프링 chaper 3 요약

토비의 스프링 chapter 3

어려운 토비의 스프링.. 1, 2장은 양은 많았지만 어느 정도 읽을 만 했지만 3장은 생각보다 더욱 안읽히는 것 같다. 이번 주 할당 받은 읽는 양은 3,4장이다. 개발도 시작하면서 더욱 읽을 시간은 없어졌고 기어코 마지막 날 까지 읽어야 하는 상황이 벌어졌다…

3장

3장은 템플릿에 관한 이야기이다. 1장 2장에서 개선의 개선을 거듭한 초난감 DAO를 가지고 좀더 발전시켜 나가본다. 앞서 작성한 코드에는 예외처리 부분이 상당히 빠져있다. 예외처리를 적용하기 위해 try-catch 문을 사용했다.

리소스의 반환

Conntection 이나 PreparedStatement 를 사용하고 마지막에 close() 를 통해 종료 시킨다. 의미로 봐서는 열린 객체를 닫는다 로 볼 수 있을 것 같은데 여기서 할당된 리소스를 반환하는 것이다. DB 커넥션이라는 제한적인 리소스를 공유해서 사용하는 서버에서는 이 리소스 반환이 필수적이다. 예외가 발생 했을때 반환하지 않으면 피명적인 문제를 일으킬 수도 있다.

새로 알게된 사실 close 또한 예외처리를 해줘야 한다.

connection c = null;
try {
    // ...
} catch (SQLException e) {
    // ...
} finally {
    if ( c != null ) {
        try {
            c.close() ;
        } catch (SQLException e) { }
    }
}

변하는 것과 변하지 않는 것

위에 적힌 코드를 보면 try-catch 문이 중첩에다가 if조건문도 있고 등등 난잡한 코드가 생겼다. 그리고 //.. 로 작성된 부분이 실제 Connection을 가지고 수행해야할 비즈니스 로직이라고 하면 이부분은 메소드 마다 다르게 구현될 것이다.

이 비즈니스 로직은 그럼 매번 바뀌는 것이고, 이외의 예외처리하는 부분은 매번 같을 것이다. 그러면 매번 같은 예외처리가 생길텐데 그 부분마다 계속 copy & paste 할 것인가? 그럼 안되지.. 그래서 적용할 패턴이 전략패턴 이다.

전략패턴 이외에도 적용할 만한 방법으로는 메소드 추출, 템플릿 메소드 패턴사용 등이 있지만 메소드 추출은 커다란 이득을 보지 못하고 바뀌는 부분을 추출해버리는 형태라 어색하다.

템플릿 메소드 패턴은 DAO 로직마다 상속을 통해 새로운 클래스를 만들어야하는 단점과, 컴파일 시점에 클래스간의 확장 구조, 관계가 결정되어버린다는 점, 상속을 이용하므로 유연하지 못한점이 단점이다.

전략패턴 : 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 패턴

클라이언트 (deleteAll) 에서 어떤 전략 쓸껀지 주입하게 하고, 컨텍스트에서는 주입받은 전략을 사용하면 돼. 그 전략은 인터페이스로 만들어져 있고 구현체마다 다른 로직을 가지고 있으면 되지.

마이크로 DI

DI : 제 3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정되도록 만든다는 것

일반적으로 DI 는 4개의 오브젝트 사이에서 일어난다. 관계를 가질 오브젝트 2개, 이 2개의 관계를 결정해주는 오브젝트 팩토리, 그리고 이걸 사용하는 클라이언트. 인데 때로는 오브젝트 팩토리의 역할을 클라이언트에서 책임질 수도 있다. 또한 클래스 내부에서 메소드로 결정되는 경우도 있고. 작은 단위에서 DI가 이루어지는 걸 마이크로 DI라고 한다.

중첩 클래스의 종류

중첩 클래스 : 다른 클래스 내부에 정의되는 클래스

  1. 스태틱 클래스 - 독립적으로 오브젝트로 만들어질 수 있음
  2. 내부 클래스 - 자신이 정의된 클래스의 오브젝트 안에서만 만들어질 수 있음 2-1. 멤버 내부 클래스 : 오브젝트 레벨에서 정의 2-2. 로컬 클래스 : 메소드 레벨에서 정의 2-3. 익명 내부 클래스 : 이름을 가지지 않음

특별한 DI

메소드 레벨의 DI 를 적용하고, 컨텍스트가 다른 DAO에서 사용할 수 있을 만큼 범용적인 일을 하므로 클래스로 분리했다. 그러고 DAO에서는 context를 주입 받고, context에서는 datasource를 주입받게만들어 datasource 와 dao 사이에 context가 끼어들어갔다. 근데 이 과정에서 DI 원칙을 역행하는 모습을 볼 수 있다.

앞서 진행한 DI는 관계를 유연하게 하기 위해 interface로 만들고 오브젝트간의 관계를 느슨하게 만들었다. 하지만 여기서는 클래스를 주입시켰다. 앞뒤가 안맞는다.

결론 부터 말하면 책에서는 꼭 그럴필요는 없다. 란다. DI의 의미를 정확하게 곱씹어 보면 객체의 생성과 관계 설정에 대한 제어권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC 개념을 포괄한다고 한다. 그런 의미에서는 context는 스프링을 이용해 dao 객체에서 사용하게 주입했으니까 DI의 기본을 따른다고 한다.

어렵다…

스프링에서는 드물지만 클래스를 직접 의존하는 경우도 있다.

여기서 사용한 dao와 context는 매우 긴밀한 관계를 가지고 강하게 결합되었다. 그러니까 항상 함께 사용되고 dao가 바뀐다면 context도 몽땅 바뀌어야 한다. 어쨋든 엄청나게 끈끈한 사이라서 클래스 주입을 해도 된다고 판단했다 인데. 이렇게 판단, 구현 하긴 어려울 것 같다.

그리고 의문사항이 생기는데 1장에서 바뀌지 않는 코드는 없다고 했다. 그러니 인터페이스로 유연하게 대처하는 방식으로 시작하는게 무조건 옳다고 했다. 근데 여기서는 인터페이스를 굳이 안만들어도 된다고 한다. 여기도 앞뒤가 안맞는다? 라고 생각했는데 무슨 의도인지 알겠다.

코드를 이용한 수동 DI

context를 빈으로 등록하고 dao로 주입하는 거 말고 코드로 주입시킬 수도 있다. 근데 이 경우엔 빈으로 등록하려는 이유인 싱글톤으로 만들어지는 걸 포기해야한다. 그리고 누군가 context를 생성하고 초기화하는 작업을 코드로해야한다. 그러고 사용하는 datasource를 코드로 주입 받아야한다..

public class UserDao {
    private JdbcContext jdbcContext;

    public void setDataSource(DataSource dataSource) {
        this.jdbcContext = new jdbcContext();
        this.jdbcContext.setDataSource(dataSource);
        this.dataSource = dataSource;
    }
}
  빈 등록 코드 이용
장점 오브젝트간 실제 의존관계가 설정파일에 명확히 드러남 DI 원칙에 부합하지 않는 구체적인 클래스와 관계가 직접 노출됨
단점 관계가 외부로 드러나지 않는 점 빈으로 만들때의 이점을 누리지 못함(싱글톤), 부가적인 코드 필요

템플릿과 콜백

템플릿

어떤 목적을 위해 미리 만들어 둔 모양이 있는 틀 고정된 틀 안에 바꿀 수 있는 부분을 넣어서 사용하는 경우

우리가 사용하는 세계에서는 템플리 메소드를 슈퍼클래스에 두고, 바꿀 수 있는 부분을 넣고 그걸 서브클래스에서 구현해서 사용하는거에 적용할 수 있겠다.

콜백

실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말함

파라미터롤 전달되는 메소드. 이지만 자바는 파라미터로 메소드를 못 날리니까 객체를 하나 날리지.

템플릿 콜백 작업 흐름

클라이언트 -> 클라이언트 : 1. callback 생성
클라이언트 -> 템플릿 : 2. callback 전달/템플릿 호출
템플릿 -> 템플릿 : 3. workflow 시작, 4. 참조 정보 생성
템플릿 -> 콜백 : 5. callback 호출/ 정보 전달
콜백 -> 콜백 : 6. client 변수 참조, 7. 작업 수행
콜백 -> 템플릿 : 8. 작업 결과 전달
템플릿 -> 템플릿 : 9. workflow 진행, 10. workflow 마무리
템플릿 -> 클라이언트 : 11. 작업 결과 전달

템플릿과 콜백을 찾을 땐

변하는 코드의 경계를 찾고 그 경계를 사이에 두고 주고받는 일정한 정보가 있는지 확인


출처 :

  • 이 글은 이일민 지음의 ‘토비의 스프링 3.1 vol.1’ 을 읽고 작성되었습니다.
프로가 되기 위한 웹 기술 입문 - 1

웹 기술, 웹이 뭐지? 내가 생각하기에 웹 서비스도 만들고 배우면서 주먹구구식으로 필요한 것만 살짝씩 배운 경향이 있는거 같아 이번 기회에 웹 기술을 정리해보고자 한다.

웹 이란?

웹 애플리케이션

웹 애플리케이션이 뭔지 설명하자면 데스크탑 애플리케이션과 비교할 수 있겠다. 기본적으로 컴퓨터상에 설치하는 앱을 말할 수 있겠다. CD를 통해 배포되거나 인터넷으로 다운 받아 설치 할 수 있는 앱들 말이다. 예를 들면 오피스라던지 한글이라던지. 데스크탑 애플리케이션의 특징은 설치된 컴퓨터에서만 실행할 수 있고 실행 전에 설치돼야 한다.

웹 애플리케이션은 우리가 인지하지 못하는 부분에서도 접해왔고 접하고 있다. 당연하다고 생각하고 사용해 왔던 대형 포털 사이트, 인터넷 서점, 동영상 스트리밍 사이트 등 모두 웹 애플리케이션이다. 그럼 이 웹 애플리케이션은 뭐가 다른가? 왜 웹 애플리케이션이라 하는거지? 웹 애플리케이션은 설치된 본인의 컴퓨터가 아닌 서버에서 처리가 이루어진다. 또, 컴퓨터에 애플리케이션을 설치할 필요가 없다.

데스크탑 애플리케이션 vs 웹 애플리케이션

데스크탑 애플리케이션 웹 애플리케이션
주된 처리를 PC에서 진행한다. 주된 처리를 서버에서 진행한다.
화면은 운영체제의 기능으로 표시 HTML을 이용한 웹 브라우저로 표시
PC에 설치 필요 PC에 설치할 필요 없음

웹의 발전

전 세계를 이어 주는 인터넷이라는 것이 발달하게 되었고, 초기에 음성 데이터만을 주고 받는 전화의 한계를 넘어 디지털화된 온갖 정보를 주고 받을 수 있게 되었다. 초기의 인터넷은 40년 전에 ARPANET 이라는 초기 모델이 만들어 졌다. 하지만 장비들이 고가였고, 흥미가 떨어지는 문제점 때문에 보편화되지 못했다.

WWW

www www 1

이를 바꾼 계기가 WWW의 발전이라고 한다. 1989년 유럽 원자핵 연구 기관에서 논문이나 연구 내용을 공유하기 위해서 고안한 것이 지금날의 WWW 이다. 여기서 문서 내용의 공유 형식을 HTML(Hyper Text Markup Language)이라는 통일된 형식을 사용하자 제안되며 지금까지 사용하고 있다.

뒷받침 기술의 발명

웹 서버와 클라이언트

간단하게 클라이언트는 웹 서버로 원하는 자료를 요청하고 웹 서버는 클라이언트의 요청에 따라 알맞은 HTML 파일을 건네주는 구조다. 이 간단한 동작을 위해 발전된 기술들이 있다. 서버로는 아파치 HTTP 서버, MS의 IIS(Internet Information Services)가 널리 사용된다.

왜 이렇게 서버랑 클라이언트로 역할을 나눌까? 이 질문에는 WWW의 생긴 원인에서 찾을 수 있을 것 같다. 문서 공유를 하기 위해 처음 고안되었듯, 오늘날의 웹도 컨텐츠 또는 정보를 공유, 검색하기 위함인데 이런 컨텐츠 또는 정보는 불특정 다수에게 공개하고 있다. 정보는 한 곳에 정리되어 있어야 관리하기 편하기 때문이다.

URL

클라이언트는 서버로의 요청을 URL(Uniform Resource Locator)로 한다.

http://garden715.github.io/index.html

라는 URL이 있을 때 http는 스킴이라고 하고 https, ftp, file 등 이 있다. garden715.github.io 를 호스트명 이라고 하고, /index.html이 경로명이다. garden715.github.io라는 호스트에 있는 index.html의 파일을 요청 한 것이다.

HTTP

URL의 사용으로 다양한 컨텐즈, 정보를 주고 받을 수 있게 되었는데, 다양해짐에 따라 서로 주고 받는 정보에 따른 약속의 필요성이 생겼다. 이런 약속이 없다면 요청의 의미를 해석하기 힘들고, 설령 해석해서 답변을 준다 하더라도 요청자가 올바르게 이해하리라는 보장이 없다.

이러한 이유로 웹 서버와 클라이언트가 통신하기 위한 약속, 즉 프로토콜이 필요해졌다. 이것이 HTTP(Hyper Text Transfer Protocol이다.

CGI

웹 사이트는 이제 단순 문서 공유만을 위한 서비스가 아닌 더 많은 사람들을 위한 새로운 컨텐츠를 계속해서 제공하는 동적인 서비스로 바뀔 것을 요구당한다. 하지만 서비스 제공자의 입장에서 컨텐츠를 갱신하는 방식은 여간 쉬운 작업이 아니어서 컴퓨터로 하여금 편리하게 컨텐츠 갱신을 할 수 있는 환경을 갖추고자 했다.

정적 콘텐츠와 동적 콘텐츠

동적 콘텐츠

정적 콘텐츠

이런 동적 콘텐츠를 생성하기 위한 프로그램과 웹 서버간의 연동이 필요한데 이것이 CGI(Common Gateway Interface 라는 구조이다. 서버는 클라이언트가 보낸 요청을 서버상에서 작동하는 프로그램으로 보낸다. 그 프로그램에서 요청을 분석해 HTML을 생성한다음 웹 서버로 보내고 웹 서버는 클라이언트로 응답한다.

서블릿

CGI 프로그램의 문제점에 대응하기 위해 서블릿이 등장했다. 기존의 CGI 프로그램은 개발 언어의 문제와 성능 문제를 안고 있었다. 요구하는 서비스의 규모가 거대해 졌고, 요청을 모두 감당하기 힘들어 졌다. 이 문제를 보완하기 위해 자바/서블릿이 탄생했다. 이전의 문제의 해결과 더불어 멀티스레드, 보안, 통신에 표준화된 방식으로 지원했다.

서블릿은 자바로 만들어진 HTML등의 웹 콘텐츠를 생성하기 위한 프로그램인데 CGI와 비슷하다. 콘텐츠를 생성하는데 자바를 사용하니까 객체지향을 적용할 수 있고, 대규모 애플리케이션에 적합하다는 장점이 있다. 또 CGI와는 다르게 프로그램을 매번 실행할 필요가 없어 비교적 고속으로 동작한다.

CGI vs. Servlet

CGI를 이용한 웹 애플리케이션

CGI를 이용한 웹 애플리케이션

Servlet을 이용한 웹 애플리케이션

Servlet을 이용한 웹 애플리케이션

출처 :

  • 이 글은 고모리 유스케 지음, 김정환 옮김의 프로가 되기 위한 웹 기술 입문을 읽고 작성되었습니다.
  1. http://www.techspot.com/news/66067-public-first-gained-access-world-wide-web-25.html 

컴파일러와 인터프리터

컴파일러와 인터프리터의 비교

컴파일러와 인터프리터를 비교해서 정리하고자 한다. 이전에 면접에서 이 질문을 했는데 얼추 비슷한 대답을 했지만 그 당시 정리하지 않고 즉석에서 아는 내용만 말했을 뿐 어수선하게 대답한 경향이 있었다.

하드웨어는 저급언어인 기계어만 인식하고 소프트웨어는 일반적으로 자연어에 가까운 고급언어로 작성된다. 하지만 일부 속도나 크기가 매우 중요하거나 특수한 용도로 사용되는 프로그램의 경우엔 어셈블리어로 작성한다.

고급언어로 작성된 소프트웨어를 컴퓨터에서 실행하려면 컴퓨터가 인식하는 기계어로 변환해야 하는데 이때 하드웨어 계층과 소프트웨어 계층 사이의 언어 변환을 위해 컴파일러나 인터프리터를 사용한다.

컴파일러

고급언어로 작성된 원시 프로그램을 저급언어로 구성된 목적 프로그램으로 변환하는 소프트웨어이다.

고급언어 프로그램 => 컴파일러가 저급언어로 변환 => [기계어 프로그램](실행)

컴파일러의 입력은 원시프로그램이고 출력은 목적 프로그램이다. 컴파일러의 변환은 한 번만 수행되면 목적 프로그램이 만들어 지고, 이 프로그램을 계속 사용할 수 있다. 하지만 변환에 시간이 많이 걸린다는 단점이 있다.

컴파일러의 실행과정

  1. 어휘분석 (lexical Analysis)
  2. 구문분석 (Parsing)
  3. 의미분석 (Semantics Analysis)
  4. 중간 코드 생성
  5. 기계어 코드 생성

즉, 컴파일러를 사용하면 원시 프로그램을 전체적으로 분석하여 변환하므로 많은 시간이 소요되지만, 변환 과정을 한 번만 거치면 항상 실행 파일을 수행할 수 있고, 또한 프로그램을 최적화 할 수 있기 때문에 프로그램의 실행 속도가 빠르다.

일반적으로 고급언어 한 문장은 다수의 저급언어로 대체되어야 동일한 효과를 나타낸다. 저급언어로의 변환 과정이 끝나면 목적 프로그램을 실행을 위해 컴퓨터의 메모리에 적재되어야 한다. 프로그램을 실행하는 동안 원시 프로그램은 더 이상 필요 없기 때문에 메모리에서 제거될 수 있다.

인터프리터

인터프리터는 고급언어에서 하나의 명령어를 분석하여 동일한 효과를 가진 저급언어 명령어로 변환한 후 저급언어 명령어를 실행시키며, 전체 프로그램이 실행될 때까지 이 과정을 반복하는 소프트웨어이다.

[고급언어 프로그램 => 인터프리터가 저급언어로 변환 => 기계어] (실행)

인터프리터 언어로 작성된 소프트웨어는 결과물이 없기 때문에 실행할 때마다 번역작업이 필요하다. 인터프리터를 사용하면 원시 프로그램을 명령어 단위로 변환하고 프로그램 전체에 대해 분석하지 않으므로 변환 시간이 짧다. 그러나 프로그램을 실행할 때마다 변환 과정이 필요하고 프로그램을 최적화할 수 없기 때문에 프로그램의 실행속도가 느리다.

인터프리터의 프로그램 실행 과정

  1. 한 명령어를 메모리에서 가져온다.
  2. 가져온 명령어를 해석한다.
  3. 필요한 데이터를 가져온다.
  4. 명령을 실행한다.

인터프리터 언어는 컴파일러에 비해 상대적으로 작은 소프트웨어이고 새로운 컴퓨터에 프로그램을 이식하기 쉽다.

Instruction And Memory

메모리와 명령어

메모리와 데이터는 컴퓨터의 메모리에 적재돼야 CPU가 사용할 수 있다. 명령어를 살펴보기 위해서는 ‘메모리의 속성’을 알아야한다. 메모리의 속성은 워드와 주소, 주소 지정 단위로 나타낼 수 있다.

Endian

여러 개의 연속된 대상을 1차원 공간에 배열하는 방식을 말하는데 예를 들어 32비트 컴퓨터의 경우 메모리 워드에 32개의 비트를 배열하는 방법이다. 다른 의미로는 4바이트를 배열하는 방법을 말한다. 일반적으로 4바이트를 어떻게 배열하느냐를 의미한다. 엔디언 방식에서 배열의 방식에 따른 성능 차이는 없다고 알려져 있으나, 컴파일된 실행파일을 다은 아키텍처로 이식할 때는 워드 내부의 비트나 바이트 배열의 순서에 따라 의미를 달리한다.

엔디언 방식에는 주로 ‘Big Endian’과 ‘Little Endian’ 방식을 사용한다. 빅 엔디언 방식은 왼쪽에서 오른쪽으로 바이트를 배열하는 것으로, 사람이 숫자를 쓰는 방법과 마찬가지로 큰 단위의 바이트가 앞자리에 위치한다. 이 방식에서는 MSB(Most Significant Bit) 가 포함된 바이트의 주소가 워드 주소가 된다. 이에 비해 리틀 엔디언 방식은 반대 방향으로 바이트를 배열하므로 작은 단위의 바이트가 앞자리에 위치해 각 워드의 LSB(Least Significant Bit) 가 포함된 바이트의 주소가 워드 주소가 된다.

Big, Little Endian

빅 엔디언 방식의 대표적인 예는 MIPS, IBM, Motorola, Sun를 비롯한 대부분의 RISC 아키텍쳐이며, 리틀 엔디언은 Intel, DEC 등이 있다.

빅 엔디언과 리틀 엔디언의 유래

빅 엔디언과 리틀 엔디언은 조너선 스위프트의 소설 ‘걸리버 여행기’에 나오는 소인국 릴리퍼트의 이야기 중 달걀의 뭉툭한 끝(big-end)을 먼저 깨는 사람과 뾰족한 끝(little-end)을 먼저 깨는 사람 사이의 격론에서 따온 이름이다.


주소 지정 방식

컴퓨터에서 연산을 수행하려면 메모리에서 필요한 데이터의 주소를 알아야한다. 필요한 데이터인 명령어는 피연산자 필드를 사용해 데이터의 위치에 대한 정보를 제공한다. 주소 지정 방식(addressing mode)은 명령어의 일부인 피연산자 필드를 사용하여 데이터가 실제 위치한 유효 주소 (effective address) 를 결정하는 방법이다. 피연산자 필드와 데이터 실제 위치의 관계를 나타내기 위해 다음과 같은 표기를 사용한다.

  • r : 피연산자 필드가 명시하는 레지스터 주소
  • a : 피연산자 필드가 명시하는 메모리 주소
  • ea : 참조되는 데이터를 포함하는 장소의 실제 주소, 즉 유효 주소
  • Reg[x] : 레지스터 x의 내용
  • M[x] : 메모리 x번지의 내용

0-단계 주소 지정방식

0-단계 주소 지정 방식은 데이터가 있는 위치를 파악하기 위해 특별한 과정이 필요없다. 즉시 주소 지정방식과 묵시 주소 지정방식으로 구분된다.

  1. 즉시 주소 지정
    • 피연산자 필드에 필요한 데이터가 직접 들어가 있는 방식이다.
  2. 묵시 주소 지정
    • 누산기 혹은 기타 정해진 장소에 데이터가 이미 저장되어 있는 방식이다.

1-단계 주소 지정방식

1-단계 주소 지정방식은 데이터의 위치를 확정하기 위해 주소 계산이나 읽기 연산 과정을 한 번 수행하는 방식이다. 데이터의 위치를 확정하기 위해 피연산자 필드에 필요한 데이터가 위치한 주소를 저장한다. 피연산자 필드에 저장된 주소는 레지스터의 주소나 메모리 주소일 수 있다. 이 필드 값을 활용해 필요한 데이터를 참조할 수 있다.

2-단계 주소 지정방식

2-단계 주소 지정방식은 데이터의 유효주소를 확정하기 위해 주소 계산이나 읽기 연산 과정을 두 번 수행한다. 이 방식은 데이터가 메모리에 저장되므로 많은 데이터의 위치를 명시할 수 있다.

  1. 레지스터 간접 주소 지정
    • 피연산자 필드가 레지스터 주소를 명시하며, 레지스터의 내용이 데이터를 위한 메모리의 주소를 명시한다.
    • eg = Reg[r]
      
  2. 메모리 간접 주소 지정
    • 레지스터 간접 주소 지정과 비슷하게 피연산자 필드가 메모리 주소를 명시하고 메모리 주소에 있는 값이 데이터이다.
    • eg = M[a]
      
  3. 변위 주소 지정
    • 명령어에 포한된 2개의 피연산자 필드를 이용해 데이터가 있는 메모리의 주소를 확정하는 방식으로, 색인 주소 지정과 베이스 주소지정이 있다.
    • eg = Reg[r] + a
      
  4. PC 상대 주소 지정
    • 베이스 레지스터로 프로그램 계수기를 사용하는 베이스 주소 지정의 특별한 경우이다. 변위 주소를 명시할 피연산자 필드 하나만 있으면 된다. 피연산자 필드에는 오프셋이 들어가 있다.
    • eg = PC + a
      

명령어 집합

하드웨어가 이해할 수 있는 어휘를 의미하는 명령어 집합은 복잡도에 따라 크게 CISCRISC로 나뉜다.

CISC

초기의 컴퓨터의 발전 문제

  • 메모리가 매우 고가이고 속도가 느림
  • 프로그래밍 기술의 발전이 느려 소프트웨어 개발이 어려움

당시에는 메모리 용량을 적게 차지하는 프로그램을 구성할 수 있어야 좋은 아키텍쳐였다. 코드의 밀도를 높이고 소규모 라인의 프로그램으로 많은 작업을 수행하기 위해 강력한 명령어를 집합에 추가하는 추세였다. 또한 소프트웨어 개발 비용도 만만치 않아 강력한 고급 프로그래밍 언어도 개발되더라도 이를 지원하기 위해 복잡한 명령어를 추가한 설계가 주를 이뤘다. 이와 같은 강력한 모델의 명령어 집합 구조를 CISC (complex instruction set computer) 라고 한다.

높은 코드 밀도와 강력한 명령어를 추구하려면 가변 명령어 형식을 사용할 수 밖에 없었다. 이 가변 명령어 형식은 많은 종류의 주소 지정방식을 동반하고, 연산 부호를 해독할 때 가지 명령어의 길이를 알 수 없다. 따라서 CISC 구조를 사용하면 명령어를 효율적으로 인출할 수 없고 데이터의 전송 효율도 나빠진다.

RISC

연구에 의하면 CISC 구조의 강력한 연산과 복잡한 주소 지정방식의 사용 빈도가 매우 낮으며, 일부 데이터 형식은 거의 사용되지 않는 것으로 알려졌다. 더군다나 복잡한 연산은 CPU 설계가지 영향을 미치고 데이터를 가져오는 방식에도 영향을 미치며, 제어 장치의 구성과 구현에 영향을 미치게 된다. 복잡한 명령어를 사용함으로써 간단한 명령어의 해석까지 오래 걸리게 만들며 실행시간도 증가시켰다.

이런 문제점을 인식하고 이러한 복잡한 명령어들을 포함하는 것이 바람직한지 설계자들은 의문을 품었고, 다른 각도에서 접근하기 시작했다.

  1. 단순 명령어
    • 전형적으로 하나의 사이클 내에 실행되기 때문에 실행 속도가 빠르다.
  2. 짧은 사이클 시간
    • 단순 명령어를 실행하는데 데이터 경로가 간단하므로 한 싸이클의 시간은 짧다.
  3. 적재, 저장 구조
    • 적재 및 저장 명령어를 통해서만 메모리에 접근이 허용된다.
  4. 고정 길이 명령어
    • 명령어의 길이가 고정되어 있어 fetch 과정이 빠르다.
  5. 단순 명령어 형식
    • 명령어를 빠르게 해독 할 수 있다.
  6. 제한된 종류의 주소 지정 방식
    • 유효 주소를 계산하는데 속도를 향상시킬 수 있다.
  7. 하버드 아키텍쳐

CISC RISC 비교

comparison 출처 : slideshare - Flashdomain