최근에 형이 조립식 PC를 사려고 해서 도와줬는데 이번에 운영체제를 좀 제대로 공부해보면서 먼저 컴퓨터 구조에 대해 보다보니 조립 PC를 맞췄던 것이 이해에 도움이 많이 된 것 같다. 유튜브에 널널한 개발자분도 다나와라는 조립 PC 견적 사이트에서 설명을 보면서 부품을 하나씩 보면 이해하기 좋다고 해서 보게 되었다.

 

컴퓨터는 CPU, 메인 보드, RAM, SSD, 그래픽 카드, 파워, 쿨러.. 등으로 이루어지는데 핵심은 CPU + RAM + 메인보드이다.

 

CPU만 보면 먼저 CPU에 탑재된 코어가 많으면 동시에 작업할 수 있는 양이 늘어나기 때문에 멀티태스킹 작업에 유리해진다.

 

쓰레드(Thread)는 CPU 내부에서 작업을 수행하는 가장 작은 단위로, 쓰레드의 수는 각 CPU의 코어의 수와 밀접한 관련이 있다.

 

CPU의 동작속도는 Hz라는 단위를 사용하며 이를 다른 말로는 클럭(clock, 1초 동안 파장이 한 번 움직이는 시간)이라고 한다. 이 시간 동안 데이터를 얼마나 많이 처리하느냐에 따라 CPU의 속도가 달라지게 되는데 무작정 동작속도만 높이면 CPU에서 많은 열이 발생하기 때문에 한계가 있다.

 

CPU 전체에서 하나만 존재하는 L3(Level 3) 캐시 메모리는 메인 메모리에서 CPU에 처리해야 할 데이터를 보낼 때 가장 먼저 지나게 되는 임시 저장소로 캐시 메모리는 버퍼를 사용해서 메모리로부터 데이터를 미리 읽어들여서 메모리 접근 횟수를 줄이는 등의 역할을 한다.

 

RAM(Random Access Memory, DDR4..)은 주기억장치로 HDD, SSD(보조기억장치)로부터 데이터를 읽어와 임시 저장하고 CPU에 빠르게 전달한다. 속도가 HDD에 비해 매우 빠르고 휘발성이며 CPU와 HDD 사이의 속도 차이에 따른 병목 현상을 해결해준다.

 

SSD는 반도체를 이용해 데이터를 읽거나 저장하는 보조기억장치로 디스크를 회전시키며 데이터를 읽거나 저장하는 물리적 방식의 HDD에 비해 매우 빠르고 효율적이다.

 

'CS' 카테고리의 다른 글

[운영체제] 가상 메모리  (0) 2023.04.05
[운영체제] 프로세스와 스레드  (0) 2022.12.08
[운영체제] 프로세스  (0) 2022.12.02
컴퓨터 구조와 운영체제  (0) 2022.12.01

 

이전에 작성했던 글을 복습할겸 보다가 의존 관계 주입에서 우테코 과제 코드가 생각나면서 멈칫했다. 의존 관계 주입은 애플리케이션 실행 시점에 외부에서 구현 객체를 생성해서 의존 관계가 연결되는 것으로 결합도를 낮추고 유연성을 확보할 수 있다.

 

 

[Spring] 스프링 핵심 개념 (1/2)

스프링의 핵심 원리는 다형성과 SOLID 원칙을 따르는 설계 SOLID 5 원칙 로버트 마틴이 정의한 객체 지향 프로그래밍 설계의 기본 원칙 애자일 소프트웨어 개발과 적응적 소프트웨어 개발의 전반적

treecode.tistory.com

우테코 과제에서 테스트 코드를 작성하기 어려웠던 부분이 있는데 BridgeMaker를 필드에서 초기화하면서 BridgeRandomNumberGenerator()를 생성하는 부분이였다. BridgeNumberGenerator의 구현체인 BridgeRandomNumberGenerator는 1과 0의 랜덤한 숫자를 생성해주는 역할을 한다.

 

 

BridgeGame을 테스트 할 때 랜덤하게 생성되는 숫자 때문에 테스트를 해보기가 까다로웠는데 이는 필드에서 BridgeRandomNumberGenerator를 생성하기 때문에 결합도가 높고 유연성이 떨어지기 때문이였다.

 

 

BridgeMaker를 생성자를 통해 전달을 받으면 BridgeMaker를 외부에서 생성할 수 있고 테스트를 할 때 BridgeNumberGenerator의 테스트 구현체를 별도로 생성하면 된다.

 

 

추가로 BridgeMaker는 처음에 한번 다리를 생성하는 역할만 하기 때문에 BridgeGame의 필드로 선언할 필요가 없는 것 같다.

 

 

지금 와서 다시 보니 코드를 작성하면서 SOLID 원칙을 잘 지키고 있는지 고민이 부족했던 것 같고 Lombok의 @RequiredArgsConstructor 애노테이션을 쓰면서 직접 생성자 코드를 안 만들다보니 아무 생각 없이 필드에 생성한 것 같다. (반성 ㅠ)

 

 

우테코 마지막 4주차 미션이 끝이 났다.

 

이번 미션은 메서드당 10줄 이내라는 제한과 예외가 발생한 부분부터 다시 입력을 받는 등 여러 조건들이 추가되어서 더 까다로웠고 요구 사항을 지키다보니 주요 목표였던 클래스 분리와 리펙토링에 대한 고민도 자연스레 많이 하게 되었다.

 

다리 건너기 게임은 다리 길이(3 ~ 20)를 입력 받으면 왼쪽에서 오른쪽으로 건너가는 다리를 생성한 뒤에 플레이어로부터 위, 아래 방향 입력을 받으면서 맞추면 건너가는 게임이다. 위, 아래 방향은 RandomGenerator를 통해 1 또는 0을 랜덤하게 생성하고 1이면 위, 0이면 아래 방향이 길이 되도록 만들면 된다. 이동할 수 있으면 O, 이동할 수 없으면 X를 출력하고 실패 시 재도전도 가능하다.

 

RandomGenerator를 통해 1이면 위를 나타내는 "U", 0이면 아래를 나타내는 "D"로 바꿔야 하기 때문에 Enum 클래스로 생성을 했다.

 

개인적으로 고민을 많이 한 부분은 이동할 때마다 다리 이동 결과를 출력하는 기능이었다. 맨 처음 입력을 받아 생성 되는 다리(맵)은 프로그램이 종료 될 때까지 변경되지 않고 유지되기 때문에 불변의 일급 컬렉션으로 관리를 하고 싶었다. 그래서 다리 이동 결과를 보관할 또 다른 객체가 필요했고 플레이어(Player) 클래스를 새로 생성하였다.

 

위 방향:     [ O |  X ]

아래 방향: [     |      ]

 

위와 같이 이동할 때마다 출력을 해야 되는데 처음에는 StringBuilder로 append를 해가면서 만들려고 하다가 너무 지저분하고 하드 코딩을 하는 느낌이 들어서 이동 결과 문자인 'O', 'X', ' '를 Enum 클래스인 MoveResult로 생성을 해서 List<MoveResult>를 만들었다.

 

이번 미션에서는 Enum를 많이 사용했는데 적절하게 사용하고 있는지는 아직 잘 모르겠지만 개인적으로 많은 이점을 느낄 수 있었다.

 

플레이어는 Bridge한테 다리를 건너갈 수 있는지 요청을 하고 MoveResult를 반환 받아서 이동 결과('O', 'X')를 누적해나가고 이동하지 않은 반대 방향에는 OTHER(' ') 공백 문자를 넣는다. 

 

List<MoveResult>를 toString해서 리스트의 구분자인 콤마를 출력 형식 문자에 맞춰 바꿔주었다. 리스트의 toString은 양 끝을 [, ] 괄호로 출력하기 때문에 이전처럼 StringBuilder로 하나하나 append() 하지 않고 출력을 할 수 있었다.

(지금 보니 Player를 그대로 전달하지 않고 이동 결과만 전달하거나 Dto를 따로 만들었으면 좋았을텐데..)

 

다음으로 어려웠던 부분은 예외 상황에 대한 요구사항이었다.

이전 미션에서는 예외 발생 시 프로그램 로직 진입점에서 try, catch로 잡아서 에러 메시지를 출력하고 종료했었는데 이번에는 예외가 발생한 부분부터 다시 입력을 받아야 됐다.

 

먼저 예외 메시지를 Enum으로 생성했다.

 

그리고 메서드마다 try, catch를 쓰자니 억지로(?) 막는 것 같아서 고민을 하다가 간단하게 게임 진행 상태를 나타내는 Enum 클래스를 만들었다.

 

게임 진행 상태를 단계적으로 바꿔가면서 문제가 발생하면 그 부분부터 다시 시작할 수 있도록 choiceGameMode() 메서드를 추가했는데 다시 보니 프론트 컨트롤러 클래스를 만드는 것도 괜찮았을 것 같다

이렇게 if문을 나열하니 너무 절차적인 것 같은데 더 나은 방법이 떠오르지 않아서 조금 아쉬웠다. 그리고 마지막날 리펙토링을 하다가 마감 직전에 오류를 발견해서 아슬아슬하게 고쳐서 제출했는데 단위 테스트도 중요하지만 통합 테스트도 신경을 많이 써야겠다 느꼈다. 물론 테스트 코드를 최대한 빈틈이 없도록 만들어야겠지만 놓친 부분이 있을 수 있기 때문에 직접 돌려보면서 눈으로 확인하는 테스트도 꼭 필요한 것 같다.

 

프리코스에서 만약에 기능 목록을 작성하고 커밋을 쪼개라는 등 이런 말 없이 그냥 미션을 풀라고 했으면 과연 어떻게 했을까

이전 경험들을 떠올려보면 나한테 정말 필요한 부분은 어려운 문제를 하나 하나 쪼개나가면서 풀어내는 연습이 아니었나 생각이 들었다.

우테코 3주차 미션은 로또 게임이 주제였다.

코로나에 걸려서 2주차 미션을 아쉽게 마무리하고 3주차때도 컨디션이 좋지 않아서 걱정했는데 그래도 시간이 지나면서 조금씩 괜찮아지는 것 같았고 미션 진행에 대한 만족도도 저번보다 높게 마무리 지을 수 있었다.

 

3주차의 주요 목표는 클래스를 분리하는 연습과 도메인 단위 테스트를 작성하는 것이었고 로또 게임은 금액을 입력하면 랜덤 번호가 생성이 되고 당첨 번호를 직접 입력해서 당첨 결과와 수익률을 보여주면 된다. 

 

기능 목록도 몇번 작성해보면서 점점 감이 잡히는데 저번 주차 피드백을 보고 이번에는 체크 박스를 써서 체크를 해가면서 하나씩 차근차근 구현을 해나갔다. 체크 박스가 별거 아닌거 같았는데 진행 상황도 눈에 확 들어오고 개인적으로 효과가 좋았다.

이전에는 기능 목록을 작성하는 과정을 생략해왔는데 프로젝트를 할때 이렇게 기능 목록을 만들고 하나씩 해나가면서 진행했다면 벅차다고 느꼈던 부분들도 잘 해결하지 않았을까 하는 아쉬움도 들었다. 다음 프로젝트를 한다면 이렇게 기능 목록을 정리하고 작게 쪼개서 풀어나가는 식으로 진행을 해봐야겠다.

 

 

먼저 로또 번호를 LottoNumber 클래스로 만들어서 로또 번호 자체적으로 생성을 할 때 검증을 하도록 하였다. 이렇게 함으로서 로또 번호를 안심하고 사용할 수 있는 이점이 있지만 번호 하나 하나를 클래스로 생성하니 무겁다는 느낌도 받았고 불편한 부분도 있었다. 이번에는 연습을 하면서 클래스를 최대한 분리해보려고 노력하였다.

 

 

로또 클래스는 LottoNumber의 리스트를 가진 일급 컬렉션으로 생성을 할 때 로또에 대한 유효성 검증을 하는데 다시 보니 검증에 대한 처리밖에 없어서 다음에는 일급 컬렉션에 행위를 추가해보면 좋을 것 같다. 원래는 파싱하는 로직도 들어가 있었는데 값을 파싱하는 건 Lotto클래스의 역할이 아닌 것 같아서 분리를 해버렸다.

 

 

테스트 코드는 @ParameterizedTest를 사용해서 중복을 제거하고 효율적으로 작성하려고 노력을 했다. 이전에 ValueSource로 간단한 입력값을 테스트한 적은 있었는데 이번에는 MethodSource를 사용해서 더 복잡한 입력값도 간단하게 반복 테스트 해볼 수가 있었고 개인적으로 너무 만족스러웠다. (이렇게 테스트 하는게 맞는지는 아직 잘 모르겠다)

 

 

이번 미션에서는 도메인 클래스로 분리를 많이 해보고 테스트 코드를 작성을 열심히 해봤는데 회고를 쓰면서 다시 코드를 쭉 보니 메서드 분리나 네이밍 처리에서 아쉬운 점들이 많이 보였다. 다음 미션에는 메서드 분리와 네이밍 처리에도 신경을 써보고 일급 컬렉션의 이점도 더 활용해봐야겠다.

+ Recent posts