블로그

suover

인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 1주차 발자국

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기강의와 함께한 인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 (Kotlin, Spring Boot) 1주차 발자국 입니다. 강의 수강이번 주에는 웹 개발의 기본 개념을 학습하며, Spring Boot를 중심으로 한 웹 개발에 대한 전반적인 내용을 다루었습니다. 특히, 웹 서비스의 구성 요소인 클라이언트, 서버, 데이터베이스의 상호작용 방식을 이해하고, 클라이언트와 서버가 데이터를 주고받는 과정에서 발생하는 HTTP 통신과 REST API의 개념을 명확히 정리했습니다.Spring Boot를 활용한 프로젝트 구조와 레이어드 아키텍처(Controller, Service, Repository)를 학습하며, 각 레이어의 역할과 책임에 대해 더욱 깊이 이해할 수 있었습니다. 또한, MVC 패턴과 디자인 패턴의 중요성도 학습하며, 어떻게 하면 코드의 결합도를 낮추고 유지보수성을 높일 수 있는지 고민하는 시간을 가졌습니다. 회고일주일간 강의를 들으면서 웹 개발의 전반적인 흐름과 기본 개념을 더 체계적으로 정리할 수 있었습니다. 이전에도 어느 정도 개념을 알고 있었지만, 이번에는 MVC 패턴이나 레이어드 아키텍처의 필요성에 대해 더 명확히 이해하게 되어 좋았습니다. 프레임워크와 라이브러리의 차이도 명확해져, 앞으로 어떤 상황에서 어떤 도구를 선택할지에 대한 기준이 생긴 것 같아 뿌듯합니다.칭찬할 점매일 꾸준히 강의를 들으며 웹 개발의 기본기를 다진 점학습한 내용을 직접 실습 프로젝트에 적용하면서 이론과 실제를 연결 지은 점이번 주 계획했던 학습 목표를 모두 달성한 점 아쉬웠던 점스프링의 의존성 주입(Dependency Injection)에 대한 이해가 조금 부족하다는 느낌이 있었습니다. 강의에서 개념을 배웠지만, 실제로 이를 프로젝트에 어떻게 적용해야 하는지 고민하는 시간이 좀 더 필요할 것 같습니다. 보완할 점의존성 주입을 주제로 더 깊이 파고들어 실습해보고, 관련된 자료를 찾아보면서 이해도를 높여야겠습니다. 특히, 생성자 주입 방식을 더 명확하게 습득하고 싶습니다. 다음 주 목표다음 주에는 이번 주에 학습한 내용을 바탕으로 더 구체적인 프로젝트를 진행하며, 의존성 주입과 스프링 빈 관리에 대한 이해를 심화할 계획입니다. 이를 통해 Spring Boot의 기능을 제대로 활용할 수 있도록 하겠습니다.미션이번 주 미션은 미니 프로젝트 개발을 주제로, 웹 개발의 기본적인 데이터베이스 테이블을 설계하고 이를 GitHub에 업로드하는 것이 목표였습니다. 프로젝트의 주제는 다양한 활동에서 함께할 사람을 찾고 소통할 수 있는 게시판 서비스로, 사용자, 카테고리, 게시글, 댓글, 그리고 좋아요 기능을 포함한 테이블을 설계했습니다. 미션 과정테이블 설계 사용자 테이블 (users): 사용자의 이메일, 비밀번호, 이름, 닉네임 등을 저장하며, user_role 필드로 사용자 권한을 설정할 수 있습니다.카테고리 테이블 (categories): 게시글의 카테고리를 관리하기 위해 설계된 테이블로, 카테고리 이름을 저장할 수 있습니다.게시글 테이블 (posts): 사용자가 작성한 게시글을 저장하는 테이블로, 사용자와 카테고리와의 관계를 참조하도록 했습니다.댓글 테이블 (comments): 게시글에 달린 댓글을 저장하는 테이블로, 답글까지 고려한 설계를 진행했습니다.좋아요 테이블 (post_likes, comment_likes): 게시글과 댓글에 대한 '좋아요' 기능을 구현하기 위한 테이블을 설계했습니다.  프로젝트 깃허브에 업로드미니 프로젝트를 깃허브에 올리는 작업을 진행했습니다. 이번 작업을 통해 버전 관리와 협업의 중요성을 다시 한번 확인할 수 있었습니다. 회고테이블 설계를 처음부터 끝까지 진행하면서, 데이터베이스 간의 관계를 설계하는 것이 얼마나 중요한지 다시 한번 깨달았습니다. 또한, 테이블 관계를 명확히 이해하지 못했던 부분에서 여러 번 수정을 거치면서 실무적인 감각도 더 키울 수 있었습니다. 특히, 외래 키 제약 조건을 걸어 테이블 간의 참조 무결성을 유지하는 방법을 다시 복습하게 된 것이 큰 수확이었습니다.아쉬운 점테이블 설계를 처음 진행할 때, 부모-자식 관계나 외래 키 제약 조건을 제대로 설정하지 못해 몇 번의 수정을 반복해야 했습니다. 이를 통해 초기 설계의 중요성을 다시 한번 느끼게 되었고, 더 많이 연습해야 한다는 생각이 들었습니다.보완할 점초기 설계 단계에서 더 철저하게 준비하여, 데이터베이스 구조를 명확하게 하고 오류를 최소화하는 방향으로 나아갈 계획입니다.이번 주는 강의와 미션 모두에서 많은 것을 배웠고, 실습을 통해 이론을 실제 프로젝트에 적용하면서 자신감을 얻었습니다. 다음 주에도 꾸준히 학습하며 부족한 부분을 보완해 나갈 계획입니다.

백엔드인프런워밍업클럽스터디백엔드프로젝트발자국회고SpringBoot

yeonjin1939

[인프런 워밍업 클럽 CS 2기] 1주차 미션

운영체제 1. while(true){ wait(1); // 1초 멈춤 bool isActivated = checkSkillActivated(); // 체크 } 위 코드는 1초 마다 플레이어가 스킬을 사용했는지 체크하는 코드입니다. 이 방식은 폴링방식입니다. 1초마다 체크하기 때문에 성능에 좋지 않습니다. 이를 해결하기 위한 방식으로 어떤 걸 이용해야 할까요? ☞ CPU와 입출력 장치의 통신 방식 중 인터럽트 방식으로 변경해야 합니다. 폴링방식은 위에 나온 대로 입출력이 완료되는 시점을 몰라 주기적으로 확인해줘야 해 성능이 낮습니다. 이 문제점을 해결하기 위해 인터럽트 방식을 이용해야 합니다. 인터럽트 방식은 입출력 관리자에게 입출력 명령을 내린 뒤 CPU는 다른 작업을 진행하고, 입출력 완료 시 입출력 관리자가 CPU에게 신호를 주면 신호를 받은 CPU는 인터럽트 서비스 루틴(ISP)을 실행시켜 작업을 완료하여 CPU가 계속해서 다른 작업을 수행할 수 있습니다.  프로그램과 프로세스가 어떻게 다른가요? ☞ 프로그램(Program)은 애플리케이션 또는 앱이라고 하며, 사용자가 원하는 일을 처리할 수 있도록 프로그래밍 언어를 사용해 수행 절차를 표현해 놓은 명령어들의 집합입니다. 프로세스(Process)는 이 프로그램이 메모리에 올라가 실행 상태가 됐을 때의 '실행중인 프로그램'이다. 멀티프로그래밍과 멀티프로세싱이 어떻게 다른가요? ☞ 멀티 프로그래밍은 CPU의 효율을 높이기 위해 고안된 것으로, 메모리에 여러 개의 프로세스가 동시에 올라가 있어 하나의 프로세스에서 입출력 작업 발생 시 이 작업이 완료될 동안 다른 프로세스를 실행할 수 있도록 합니다. 반면 멀티 프로세싱은 여러 개의 프로세서(CPU)가 서로 협력하여 작업을 병렬 처리함으로써 많은 양의 작업을 빠른 시간에 처리할 수 있도록 한 방식입니다. 운영체제는 프로세스를 관리하기 위해서 어떤 것을 사용하나요? ☞ 프로세스가 생성될 때, 운영체제는 해당 프로세스의 정보를 가지고 있는 PCB(Process Control Block)를 생성, 저장해 프로세스를 관리합니다. PCB는 다음과 같은 요소들로 구성됩니다.PCB의 구조1) 포인터: 프로세스의 한 상태에서 다른 상태로 전환될 때 저장2) 프로세스 상태: 현재 프로세스의 다섯 가지 상태(생성, 준비, 실행, 대기, 완료) 표시3) 프로세스 ID: 프로세스 식별 숫자 저장4) 프로그램 카운터: 다음에 실행될 명령어의 주소를 포함하는 프로그램 카운터 저장5) 레지스터 정보: 프로세스가 실행될 때 사용했던 레지스터 값 저장6) 메모리 관련 정보: 프로세스가 메모리에 있는 위치 정보, 메모리 침범을 막기 위한 경계 레지스터 값 등 저장7) CPU 스케줄링 정보: CPU 스케줄링에 필요한 우선순위, 최종 실행 시간, CPU 점유 시간 등 저장 컨텍스트 스위칭이란 뭔가요?  ☞ 컨텍스트 스위칭(Context Switching)이란 프로세스 실행 중 다른 프로세스를 실행하기 위해 실행 중이던 프로세스를 저장하고 다른 프로세스의 상태 값으로 교체하는 작업입니다. 컨텍스트 스위칭이 발생하는 이유는 CPU 점유 시간 초과, I/O 요청 발생, 다른 종류의 인터럽트 발생 등이 있습니다. 프로세스 A와 프로세스 B의 컨텍스트 스위칭이 일어나는 과정은 다음과 같습니다.1) 실행 중이던 프로세스 A의 CPU 점유 시간 초과2) 운영체제가 인터럽트 발생3) 프로세스 A는 하던 일을 중단하고 현재 CPU의 레지스터 값 등을 PCB A에 저장4) PCB B를 참조해 이전 프로세스 B의 상태로 CPU의 레지스터 값 설정, 다음 명령어부터 다시 실행5) 실행 중이던 프로세스 B의 CPU 점유 시간 초과6) 운영체제가 인터럽트 발생7) 프로세스 B는 하던 일을 중단하고 현재 CPU의 레지스터 값 등을 PCB B에 저장8) PCB A를 참조해 이전 프로세스 A의 상태로 CPU의 레지스터 값 설정, 다음 명령어부터 다시 실행   자료구조와 알고리즘 여러분은 교실의 학생 정보를 저장하고 열람할 수 있는 관리 프로그램을 개발하려고 합니다. 이 때 여러분이라면 학생의 정보를 저장하기 위한 자료구조를 어떤 걸 선택하실 건가요? 이유를 함께 적어주세요. ☞ 교실의 학생 정보 데이터는 삽입 또는 삭제 발생이 거의 없고 열람이 주된 목적이기 때문에 참조 성능이 O(1)인 배열 자료구조를 선택합니다. 여러분은 고객의 주문을 받는 프로그램을 개발하려고 합니다. 주문은 들어온 순서대로 처리됩니다. 이 때 여러분이라면 어떤 자료구조를 선택하실 건가요? 이유를 함께 적어주세요. ☞ 먼저 들어온 데이터가 먼저 처리되는 선입선출(FIFO, First In First Out)의 특성을 가지는 큐(Queue) 자료구조를 선택합니다. 데크(Deque) 자료구조로도 이러한 처리가 가능하기 때문에 데크 자료구조를 이용해도 좋다고 생각합니다.

워밍업클럽미션

dd

발자국 1주 차

본 내용은 인프런 워밍업 클럽 스터디를 진행하면서 작성한 회고 글 입니다.강의: Readable Code: 읽기 좋은 코드를 작성하는 사고법배운 것절차형 프로그래밍 방식으로 구현된 '지뢰찾기'게임을 여러 추상화 기법과 객체지향 원칙을 적용해서 리팩토링하는 과정을 진행했다.섹션 1에서 간단한 강의 구성과 프로젝트 설명을 듣고 섹션 2부터 코드를 가지고 리팩토링을 하게된다. 섹션 2에서 소개한 추상화 기법은 다음과 같다.이름 짓기메서드 선언부추상화 레벨매직 넘버, 매직 스트링여기서 이름 짓기, 메서드 선언부, 매직 넘버/스트링에 대한 내용은 이미 어느정도 알고 있었던 내용이기도 하고 실제 프로젝트를 진행할 때도 고민을 했었던 부분이었다. 그래서 내가 알고 있는 내용 중에서 틀린 부분이 있는지에 집중하면서 들었던 것 같다.반면, 추상화 레벨 파트에서 소개해주신 내용은 내가 무의식적으로 수행했었던 내용이었다. 프로젝트를 진행하면서 구현 내용이 길게 작성이 되면, 코드를 읽기 불편하다는 기분이 들어서 별도의 메서드로 분리하곤 했는데, 이게 강의에서 소개한 추상화 레벨을 맞추는 행위였다. 확실히 추상화 레벨을 맞춘 메서드가 더 읽기 편하게 느껴졌고, 앞으로 프로젝트를 진행할 때 이 내용은 완벽하게 적용을 해야겠다고 생각했다.섹션 3에서는, 뇌가 저장해야 하는 인지적 부하를 줄여서 읽기 쉬운 코드를 만드는 방법을 배운다.Early return사고의 depth 줄이기공백 라인을 대하는 자세부정어를 대하는 자세해피 케이스와 예외 처리이 중 예외 처리 파트에서, 복구할 수 있는 예외와 없는 예외를 구분해서 처리하는 것이 인상깊었다. 나머지 Early return 같은 내용은 아래에 적을 Day4미션에 적용하면서 실전 감각을 길렀다.섹션 4와 5는 강의에서 가장 긴 섹션이면서, 실제 코드의 변경이 매우 많이 일어나는 파트였다. 섹션 4에서 소개된 SOLID 원칙을 적용하면서 변경이 용이한 코드로 구조가 개선되었으며, 섹션 5의 VO, Enum, 일급 컬렉션 등을 적용해서 하나의 의미있는 객체로 승급시키고, 외부에 산재되어 있던 로직을 객체가 책임지게 하는 방법을 알게 되었다.보완할 점1주 차에 진행되었던 섹션 5까지의 강의 분량이 10시간 정도여서, 1주일이면 여유있게 듣고 적용할 수 있을거라고 생각했었다. 그런데 강의에서 들었던 내용을 요약 정리하고, 리팩토링도 직접 따라하면서 들으니까 실제로 걸리는 시간이 정말 많이 들었다. 그래서 강의에서 진행된 리팩토링을 그대로 따라 치는 방식으로 듣게 되었는데, 원래 코드로 돌아가서 내가 혼자 진행한다면 이런 구조의 코드를 만들어낼 수 있을지에 대한 의문이 들었다. 많은 연습이 필요하다고 느껴져서 다음 주에는 조금 더 많은 시간을 투자해야겠다는 생각이 들었다. 그리고 이번에 배운 내용은 자바 프로그램 뿐만 아니라 모든 프로젝트에 적용이 가능하고 정말 기본이 되는 내용이기 때문에, 매 프로젝트를 할 때마다 이번에 배운 내용을 의식적으로 적용하는 연습을 해야겠다.Day2, Day4 미션1주 차에는 Day2와 Day4 2개의 미션이 주어졌다. Day2 미션은 추상과 구체에 대한 예시를 작성해보는 간단한 미션이었다. 나는 섹션 2의 추상과 구체 강의를 참고하여 사물을 보는 과정을 추상과 구체 레벨로 표현하여 미션을 제출했다. Day4 미션은 아래의 코드를 섹션 3에서 배운 인지적 부하를 줄이는 방법을 사용해서 리팩토링 하는 것이었다.public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; }주문 객체의 유효성을 검증하는 로직인데, 중첩된 if-else로 로직을 한 번에 알아보기 힘들다. 이를 개선하고자 Early return을 사용하여 depth를 줄이고 코드를 읽는 사람이 기억해야 하는 정보를 최소화했다.public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } return true; }이전 코드에 비해서 확실히 읽기 수월하지만, 두 번째 if문을 읽을 때 "주문의 총 금액이 0보다 큰 게 아니면~" 이런 식으로 읽힌다. 부정어 때문에 앞에서 읽은 내용을 반대로 생각해야 하는 불필요한 부하가 생긴다. 부정어를 없애고, 추가로 order의 총 금액이나 주문 항목을 get으로 꺼내지 않고 주문 객체에게 묻는 방식으로 코드를 개선했다.public boolean validateOrder(Order order) { if (order.isEmpty()) { log.info("주문 항목이 없습니다."); return false; } if (order.isInvalidTotalPrice()) { log.info("올바르지 않은 총 가격입니다."); return false; } if (order.isCustomerInfoMissing()) { log.info("사용자 정보가 없습니다."); return false; } return true; }개선된 코드는 부정어를 제거하여 자연스럽게 읽힌다. 그리고 주문 항목이나 총 금액을 꺼내지 않고 적절한 메서드를 만들었기 때문에 재사용성과 응집도가 향상되었고, 결합도는 낮추는 효과를 얻을 수 있었다.SOLID에 대하여 자기만의 언어로 정리하는 것도 미션 내용에 있었는데, 강의를 들으면서 정리했던 걸 간략하게 요약해서 제출했다.간단 회고강의에 대한 내용으로 미션이 주어졌기 때문에, 학습한 것을 실제로 적용해볼 수 있어서 재미있었고, 앞으로 개발을 할 때 이 내용은 확실하게 적용을 할 수 있겠다는 자신감을 얻었다. 생각보다 더 빡빡한 일정이지만 남은 3주도 열심히 해서 많은 내용을 내 것으로 만드는 시간이 되도록 할 것이다.

dd 1개월 전
tjqwns9282

인프런 워밍업클럽 2기 BE 클린코드 & 테스트 1주차 회고

강의 수강해당 글의 내용은, Readable Code: 읽기 좋은 코드를 작성하는 사고법 강의 | 박우빈 - 인프런 (inflearn.com) 강의의 섹션 1 - 5 내용을 기반으로 일주일간(첫 주차) 학습한 내용을 회고로적은 글입니다.강의섹션 2. 추상추상이 무엇인지, 구체란 개념을 확고히하고 다양한 추상화 기법과 추상화 레벨에 따른 개념과 실습섹션 3. 논리, 사고의 흐름가독성과 코드의 최적화를 하는 방법과 실습섹션 4. 객체 지향 패러다임객체의 역할, 책임, 협력에 집중하여 설계 이론과 적용 실습SOLID에 대한 개념과 실습섹션 5. 객체 지향 적용하기상속, 조합, VO, 일급 컬렉션 등 객체 지향이라는 개념을 코드에 적용하는 것에 대한 개념과 실습  실습은 주로 fork한 지뢰찾기 게임의 코드를 토대로 Refactoring을 거쳐가는 방식으로 학습하였습니다.  미션Day2 미션 (첫번째 미션) 은 추상과 구체에 대한 예시와 자신만의 해석을 표현Day4 미션 (두번째 미션) 은 SOLID에 대한 자신만의 해석 표현과 주문 검증 메서드를 섹션 3에서 배운 내용들을 토대로 Refactoring후자가 더 정리 뿐 아니라 적용을 해보는 미션인지라 좀 더 고민을 가지게 되면서도 더 나은 방식이 있을까하며 고민하는 점이 있게되 더 재밌게 할 수 있었습니다.1주차 회고강의는 처음 fork한 지뢰찾기 게임 코드를 기준으로 Refactoring을 거쳐가는 방식으로 하였다. 해당 과정에서 나는 Refactoring을 거칠 수록 더 많은 변수 생성으로 인한 개발자의 편의를 위한 코드 방식이지 않을까 싶었다. 사용되는 변수들 또는 객체들이 계속 늘어나기 시작했기 때문이다. 그러나 Refactoring을 거의 매 섹션, 섹션 내 하나의 강의의 주제마다 겁듭해나가는 과정에서 따라쳐보는 과정에서 느낄 수 밖에 없었다. 중간의 어느 부분만 수정이 아닌 거의 지뢰찾기 게임의 로직은 크게 건드리지 않으면서 매커니즘(?), 중간 과정 방식(?)을 크게 바꿔나가는 과정에서 나는 여태 생각이 짧았음과 실질적 경험이 부족하구나라는 것을 깨달을 수 밖에 없었다.나는 해당 과정에서 이를 깨달았다.2개의 철을 이어야한다.설명을 잘할 자신이 없어 대충 그림판으로 대체하도록 한다.2개의 철을 이어야 하는데 용접을 하면 바로 띡하고 이어진다. 그러나, 2개의 철덩어리만을 붙일 경우에는 그렇다. 그러나 우리가 쓰는 많은 전자 기기 및 기계들을 간단하지 않고 연결되어 있는 부분들이 매우 많다.이를 위해서 우리는 2개의 철의 볼트 구멍을 맞춰서 파내도록 철을 만들며, 볼트와 두 철을 이어 볼트로 고정하는 방식을 많이 사용한다. 이를 통해 분해도 쉬워짐과 동시에 2개의 철 중 하나 또는 둘 다 변경 되어도 이전에 사용되던 볼트와 맞는 홈만 만들어 맞추면 되는 것이다. 공장이라고 할 경우 이를 규격으로 맞추면 다음 생산 제품에도 이를 사용할 수 있고 남은 부품들에 대한 재사용성을 높일 수 있다. 그러나 이를 만드는데 또한 처음엔 매우 번거롭고 시간 소모, 요구 자원등이 클 것이다.나는 이번 인프런 워밍업 클럽 2기로 해당 강의를 수강하면서 이러한 생각을 가졌고 아직 부족한만큼 생각이 짧아 2개의 철을 그냥 용접해서 갖다 붙이는게 편하지 않나? 이러한 생각에서 점점 바꿔나가는 과정을 거칠 수 있었다.항상 부족하다고 생각하지만, 스스로 이러한 클린코드에 대한 지식과 경험이 부족한만큼 너무 유익한 한주였다고 생각한다.

인프런워밍업클럽2기be

도콩

인프런 워밍업 클럽2기 백엔드 - 발자국 1주차

강의: Readable Code: 읽기 좋은 코드를 작성하는 사고법### 섹션 2: 추상이 섹션에서는 "추상"과 "구체"의 개념을 확실히 이해하는 것이 중요했습니다. 추상은 불필요한 세부 사항을 제거하고 핵심 개념에 집중하는 방식이었는데, 이를 통해 문제를 더 간단하게 다룰 수 있었습니다. 반대로 구체는 추상적 개념을 현실에 맞게 구체화하는 과정이었습니다. 다양한 추상화 기법과 레벨을 학습하면서, 코드에서 추상화를 어떻게 활용해야 시스템을 더 유연하게 설계할 수 있는지를 배울 수 있었습니다.### 섹션 3: 논리, 사고의 흐름코드를 작성할 때 논리적 흐름과 가독성을 고려하는 방법에 대해 다뤘습니다. 코드가 복잡해질수록 논리적인 흐름을 유지하는 것이 중요하며, 이를 통해 가독성이 크게 향상된다는 것을 깨달았습니다. 논리적 흐름이란 코드가 순서대로 자연스럽게 이해될 수 있도록 작성하는 것을 의미하며, 복잡한 코드도 쉽게 이해할 수 있도록 만드는 기법을 익혔습니다.### 섹션 4: 객체 지향 패러다임객체 지향 패러다임은 객체가 자신의 역할과 책임을 명확히 갖고, 다른 객체와 협력해 더 큰 작업을 수행하는 방식을 중심으로 다뤄졌습니다. 객체 간의 상호작용을 설계하고 이를 코드로 구현하는 방법을 학습했으며, 특히 SOLID 원칙을 깊이 있게 배우며 객체 지향 설계를 강화할 수 있었습니다.#### SOLID 원칙- Single Responsibility Principle(단일 책임 원칙): 객체는 하나의 책임만 가져야 한다는 원칙입니다.- Open/Closed Principle(개방-폐쇄 원칙): 객체는 확장에는 열려있고, 수정에는 닫혀 있어야 합니다.- Liskov Substitution Principle(리스코프 치환 원칙): 서브 타입은 언제나 기본 타입으로 치환 가능해야 한다는 원칙입니다.- Interface Segregation Principle(인터페이스 분리 원칙): 특정 클라이언트를 위한 인터페이스를 분리하는 것이 중요하다는 원칙입니다.- Dependency Inversion Principle(의존 역전 원칙): 고수준 모듈이 저수준 모듈에 의존하지 않도록 설계해야 한다는 원칙입니다.### 섹션 5: 객체 지향 적용하기이 섹션에서는 객체 지향 개념을 실제 코드에 적용하는 법을 배웠습니다. 상속과 조합을 통해 객체 간의 관계를 설계하고, VO(Value Object), 일급 컬렉션 같은 개념을 사용하여 데이터를 효율적으로 관리하는 방법을 익혔습니다. 이러한 기법을 통해 복잡한 시스템에서도 유연하고 재사용 가능한 코드를 작성할 수 있었습니다.### 미션- Day 2: 추상과 구체 이해하기 이 미션에서는 "추상"과 "구체"의 개념을 나만의 언어로 설명하고, 이를 예시로 코드에 표현해보는 작업을 했습니다. 추상화를 적용한 코드와 구체적인 코드를 비교하면서 각각의 코드가 어떤 상황에서 적합한지를 분석했습니다.- Day 4: 코드 리팩터링 및 SOLID 원칙 적용 주어진 코드를 가독성 좋게 리팩터링하고, 배운 SOLID 원칙을 나만의 언어로 다시 정리하는 미션을 수행했습니다. 리팩터링 과정에서 SOLID 원칙을 적용하는 방법을 고민했고, 리팩터링 전후의 코드를 비교해 성능과 가독성에서 어떤 개선이 이루어졌는지 평가했습니다.### 회고이 강의를 통해 어렴풋이 알고 있던 개념들을 명확하게 정리할 수 있었습니다. 특히 추상과 구체의 개념을 이해하고 이를 적용하는 것이 생각보다 어렵다는 것을 깨달았습니다. 실제로 프로젝트에 적용하려면 더 많은 연습이 필요하다는 것도 느꼈습니다. 미션을 수행하면서 코드의 가독성과 객체 지향 설계를 더 깊이 이해할 수 있었고, 특히 SOLID 원칙을 내 코드에 적용하는 과정에서 많은 고민을 했습니다. 강의를 듣기 전 나름대로 리팩터링을 시도해 봤지만, 강의를 통해 배운 내용을 적용하니 더 개선할 부분이 많았다는 것을 깨달았습니다. 앞으로 더 많은 실습을 통해 이 개념들을 내 것으로 만들어야겠다고 생각했습니다.

인프런 워밍업 클럽 2기 백앤드 프로젝트 - 1주차 발자국

부트 캠프 경험도 없고 설계단계 부터 프로젝트를 진행했던 경험도 많이 없었는데 일단 지금 당장 다니는 회사에선 이직생각이 크기 때문에 친구 소개로 이런 프로그램이 있다는 사실에 반가웠습니다.개인적으로 Kotlin에 대한 경험은 없지만 Kotlin 학습과 병행하면서 프로그램을 끝까지 완주하는게 목표이고 소개해준 친구와 으쌰으쌰하면서 열심히 해보고 싶은 마음입니다. 색션1. 강의소개강의의 전반적인 흐름을 알 수 있어서 좋았다.색션2. 웹 개발 기본과 프로젝트 준비아직은 간단한 설계와 패키지 구조라 이해가 어렵지 않았다.색션3. 개발-Domain깃허브를 CLI환경이 아닌 인텔리제이를 활용하여 GUI로 적용해보는것이 처음이었고 새로운 것을 배워서 재밌었다.클래스 생성을 하는데 테이블을 보고 엔티티를 어떻게 설계해야 하는지는 아직 까지 어려운 부분이 많다 학습이 필요할 것 같다하필 회사에서 프로젝트 일정과 겹쳐 휴일에도 못 쉬고 일주일 내내 야근을 하는 상황이어서 체력도 많이 떨어지고 처음에 마음 먹은 것과 다르게 학습에 소홀해지는 기분이었다. 물론 회사 일이 우선이긴 하지만 컨디션 조절도 잘하고 시간도 잘 배분해서 핑계 대지 않도록 해야겠다... 다음주까지 프로젝트 일정으로 바쁘지만 좀 더 열심히 하겠습니다!!!

진명인

[워밍업 클럽 2기 - 백엔드 클린 코드, 테스트 코드] 1주차 발자국

본 글은 인프런에서 진행하는워밍업 클럽 2기 - 클린코드&테스트반박우빈 - Readable Code : 읽기 좋은 코드를 작성하는 사고법강의를 회고하면서 작성했습니다.강의에서 보고 배우는 것들이 정말 중요하다는 것은 항상 알고 있었지만,개인 프로젝트든, 팀 프로젝트든 강의에서 배우는 것들을 제대로 적용해본적은 없었다.그래서 이참에 강의 내용을 회고하고 복습하며 몸에 습관으로 남기고자 했다.크게 섹션별로 나눠 내가 배운 것들을 정리해보겠다.분량이 좀 많습니다.섹션 2 - 추상 이 섹션에 배운, 추상화의 원칙을 적용해서 지뢰게임 시스템을 일부 리펙터링해 볼 것이다. 추상화라는 것은 클린 코드를 관통하는 하나의 개념이라고 할 수 있다.쉽게 말하면, 중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략해 버리는 것이다.추상에 반대 개념인 구체에서 정보를 함축하고 제거하면 추상이된다. 구체와 추상은 이분법적으로 나뉘지 않는다.추상화도 단계적으로 나뉘며, 상대적인 개념이라 할 수 있다. 예를 들어, 운영체제는 하드웨어에 비해 추상화되었고애플리케이션은 운영체제에 비해 추상화되었다. 추상화는 구체를 유추할 수 있도록 해야한다.추상화 과정에서 중요한 정보를 부각시키지 못하거나,해석자들이 동일하게 공유하는 문맥에서 벗어난다면 추상으로부터어떤 구체에서 파생된 것인지 분석할 수 없다. 이 추상을 가장 쉽게 접할 수 있는 방법은 바로 '이름 짓기'이다.추상적 사고는 표현하고자 하는 구체에서 정말 중요한 핵심 개념만을추출하여 잘 드러낸 표현을 뜻한다.이때, 도메인의 문맥 안에서 이해될 수 있는 일반적인 용어여야한다. 개념적인 말이 길었는데, 이 '이름 짓기'라는 추상화 작업을 언급한 이유는우리가 정말 친숙하게 사용하고 있던 추상화 작업 중 하나이기 때문이다. 우리가 프로젝트를 개발할 때, main 메서드에 모든 로직을 때려박지 않고기계적으로 특정 작업들은 이름을 부여해 따로 분리했을 것이다.이때, 이름을 부여해 메서드로 분리해낸 것이 바로 추상화 작업이라 할 수 있다. 특정 로직에 이름을 부여해줌으로써, 단순히 프로그래밍 언어 몇줄로 작성되어 있던코드 덩어리가 의미가 생기고 여러 방면에서 활용될 수 있게되었다. 이때, 이 메서드를 만들 때 몇가지 유의할 점이 있다. 1. 메서드가 수행하는 '구체'적인 작업을 메서드명이 잘 추상화하여 표현하고 있는지 2. 메서드가 2가지 이상의 일을 수행하는지는 않는지 3. 충분히 포괄적인 의미를 담아 이름을 지었는지 모든 로직이 때려박혀 있던 기존 main 메서드에갑자기 추상화된 특정 메서드가 생긴다는 것은,추상화 작업을 통해 새로운 경계가 생겨난 것을 의미한다. 메서드로 분리된 곳을 내부 세계라고 한다면, 이 내부 세계는 추상화 레벨이 낮다고할 수 있다. 외부 세계에 비해 구체적인 특정 작업을 수행하기 때문이다.반대로 기존 여러 로직들이 엉켜있던 외부 세계는 추상화 레벨이 높다고 할 수 있다. 이제 본격적인 추상화 작업에 들어라려고 한다.오늘은 추상화 원칙에 맞춰, 여러 복잡한 로직들로 엉켜있는 기존 코드들에서특정 작업들을 추상화하여 새로운 메서드들로 만들어 내는 작업에 의의를 두겠다.public class MinesweeperGame { private static String[][] board = new String[8][10]; private static Integer[][] landMineCounts = new Integer[8][10]; <중략> public static void main(String[] args) { System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println("지뢰찾기 게임 시작!"); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); Scanner scanner = new Scanner(System.in); for (int i = 0; i < 8; i++) { for (int j = 0; j < 10; j++) { board[i][j] = "□"; } } <중략> for (int i = 0; i < 8; i++) { for (int j = 0; j < 10; j++) { int count = 0; if (!landMines[i][j]) { if (i - 1 >= 0 && j - 1 >= 0 && landMines[i - 1][j - 1]) { count++; } if (i - 1 >= 0 && landMines[i - 1][j]) { count++; } <중략> } landMineCounts[i][j] = count; continue; } landMineCounts[i][j] = 0; } } while (true) { System.out.println(" a b c d e f g h i j"); for (int i = 0; i < 8; i++) { System.out.printf("%d ", i + 1); for (int j = 0; j < 10; j++) { System.out.print(board[i][j] + " "); } System.out.println(); } <중략> char r = input.charAt(1); int col; switch (c) { case 'a': col = 0; break; case 'b': col = 1; break; <중략> 여기 누가봐도 복잡한 코드 구성이 보인다.우선, 이 지뢰찾기 게임에서 각 독립적인 기능들을 파악하고그 기능에 대한 작업은 메서드로 추상화하여 프로그램의가독성과 유지보수성을 높여보자.System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println("지뢰찾기 게임 시작!"); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");그저 게임 인트로 부분을 담당하는 이 세줄의 코드를 이름을 부여해서따로 빼주도록 하자.원하는 부분을 드래그 한 뒤, option + cmd + M 하면 메서드로 분리할 수 있다.이렇게 분리함으로써, main 메서드와 showGameStartComments 메서드간의경계가 생겼다. 지뢰찾기 게임 시작이라는 인트로 문구를 출력하는 특정 작업만을수행하고, 메서드 명을 통해 이 점이 구체적으로 드러나는showGameStartComments메서드는 main메서드보다추상화 단계가 낮다고 할 수 있다. 가독성이 높아졌을 뿐만 아니라, 추후 서비스 기획의 변경으로 인해인트로 문구가 변경되어야 할 때 복잡한 main 메서드 안을 뒤지지 않고바로 showGameStartComments의 몸체 부분을 수정해 줄 수 있을 것이다.유지보수성이 향상된 것이다. 또한, 게임의 다른 상황 혹은 다른 게임 내에서 이 메서드의 출력 부분이 필요하다면메서드 호출을 통해서 간편하게 재사용할 수 있다. 동일한 코드를 반복해서 작성할 일을 방지하므로, 코드의 재사용성도 향상되었다. 아직까지는 사실 큰 변화는 없다. 계속해서 리펙터링해보자.for (int i = 0; i < 10; i++) { int col = new Random().nextInt(10); int row = new Random().nextInt(8); landMines[row][col] = true; } for (int i = 0; i < 8; i++) { for (int j = 0; j < 10; j++) { int count = 0; if (!landMines[i][j]) { if (i - 1 >= 0 && j - 1 >= 0 && landMines[i - 1][j - 1]) { count++; } if (i - 1 >= 0 && landMines[i - 1][j]) { count++; } if (i - 1 >= 0 && j + 1 < 10 && landMines[i - 1][j + 1]) { count++; } <중략> count++; } landMineCounts[i][j] = count; continue; } landMineCounts[i][j] = 0; } }지뢰게임 초기 세팅을 위한 초기화 부분이다. 작업의 역할이 구체적이고 독립적이므로특정 메서드로 추상화하는 것이 좋을 것 같다.여기까지만 왔는데도, 많은 개선이 되었다.해당 작업에 initializeGame라는 이름을 붙여 추상화하였고,구체적인 작업 사항은 initializeGame 메서드 몸체 부분에 있고main 메서드에서는 추상화된 이름으로만 명시되어 있다.if (gameStatus == 1) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } if (gameStatus == -1) { System.out.println("지뢰를 밟았습니다. GAME OVER!"); break; }이 부분을 잘 살펴보자. main메서드에추상화된 작업명들만 잘 남고 있다가 뜬금없이gameStatus를 그저 숫자값만 비교하며 게임의 흐름을 제어하는 부분이 보인다.어떤 작업인지 유추할 수 없을 뿐 아니라, 잘 유지해가고 있던 추상화 레벨이 깨졌다. 추상화 원칙에서 정말 중요한 사항은, 하나의 계층 안에서는 추상화 레벨이 동등해야한다는것이다. 갑자기 낮은 추상화 레벨의 이 로직 부분을 추상화하고, 구체적인 작업은분리된 메서드 몸체 내부에 구현되도록하자.이제 main 메서드에서 추상화 레벨이 잘 유지되었다.추상화된 이름만 보아더 어떤 작업인지 유추할 수 있다.구체적인 구현 내부는 main 메서드 입장에서 관심없다. 메서드 명대로만잘 작동하면 상관없다.System.out.println("선택할 좌표를 입력하세요. (예: a1)"); String input = scanner.nextLine(); System.out.println("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)"); String input2 = scanner.nextLine(); char c = input.charAt(0); char r = input.charAt(1); int col; switch (c) { case 'a': col = 0; break; case 'b': col = 1; break; case 'c': col = 2; break; <중략> default: col = -1; break; } int row = Character.getNumericValue(r) - 1;벌써 숨이 턱 막힌다.유저로부터 입력받은 문자열을 파싱하고, 로직에서 사용할 데이터 타입으로 변경하는부분이 너무 장황하고, 너무 구체적이다.String cellInput = getCellInputFromUser("선택할 좌표를 입력하세요. (예: a1)", scanner); int selectedColIndex = getSelectedColIndex(cellInput); int selectedRowIndex = getSelectedRowIndex(cellInput);우리가 얻어야 하는 것은 유저가 입력한 문자가 결국 지뢰 배열에서어떤 행, 열 값인지이다.이를 메서드로 분리한다.private static String getCellInputFromUser(String x, Scanner scanner) { System.out.println(x); String cellInput = scanner.nextLine(); return cellInput; }private static int getSelectedColIndex(String cellInput) { char cellInputCol = cellInput.charAt(0); return convertColFrom(cellInputCol); }여기서 getSelectedColIndex 메서드에 주목하자. 뎁스가 한 번 더 생겼다.getSelectedColIndex은 문자열 파싱만 담당하고, 파싱한 알파벳을행, 열에 쓰일 int값으로 변환해주는 작업은 다시 convertColFrom메서드에게 맡겼다.private static int convertColFrom(char cellInputCol) { switch (cellInputCol) { case 'a': return 0; case 'b': return 1; case 'c': return 2; <중략> default: return -1; } }이렇게 되면 추상화 레벨이 상대적으로 더 나뉘었다고 할 수 있다.getSelectedColIndex은 main 메서드보다 구체적이며convertColFrom은 getSelectedColIndex 메서드보다 구체적이다.역도 성립한다.String cellInput = getCellInputFromUser("선택할 좌표를 입력하세요. (예: a1)", scanner); String userActionInput = getCellInputFromUser("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)", scanner); int selectedColIndex = getSelectedColIndex(cellInput); int selectedRowIndex = getSelectedRowIndex(cellInput);코드가 굉장히 깔끔해졌다. 구현부분들이 모두 사라지고추상화된 메서드명들을만 남았지만, 이름을 통해 그 흐름이 유추가 된다. 지금 같은 흐름으로 나머지 코드들도 똑같이 작업해준다.이제, 매직 넘버와 매직 스트링 개념을 설명하면서특정 값들을 상수로 추출하는 작업을 설명하고자 한다. 매직 넘버, 매직 스트링은 의미를 갖고 있으나 아직 상수로 추출되지 않은숫자나 문자열 등을 말한다. 이런 값들을 상수로 추출하고 이름을 지어 의미를 부여하면가독성과 유지보수성이 향상되게 된다. 현재 시스템에서 코드에서 상수로 뺄 수 있는 것들을 살표보자.private static String[][] board = new String[8][10]; private static Integer[][] landMineCounts = new Integer[8][10];지뢰게임의 판 크기였던 세로 8, 가로 10은 변하지않으며중복해서 여기저기서 쓰인다. 상수로 딱 빼기 좋은 예시이다.단축기 option + cmd + c를 통해 상수로 추출한다.게임의 인터페이스 부분을 차지하던 깃발, 땅, 지뢰 기호들도빼줄 수 있다.public static final String FLAG_SIGN = "⚑"; public static final String LAND_MINE_SIGN = "☼"; public static final String CLOSED_CELL_SIGN = "□"; public static final String OPENED_CELL_SIGN = "■";물론 여기서도 추상화의 원칙에 따라, 이름을 보고 해당 그림을 유추할 수 있어야한다.이렇게 특정 값들을 상수로 추출하면,해당 값들이 중요하게 쓰인다는 것을 나타낼 수 있고 더불어 나중에 해당 값들을 변경해야할 때 전역에서 사용되고 있는 부분 하나하나를수정하지 않고 선언부에서만 수정하면되므로, 유지보수성을 향상시킬 수 있다.public class MinesweeperGame { <중략> private static int gameStatus = 0; // 0: 게임 중, 1: 승리, -1: 패배 public static void main(String[] args) { showGameStartComments(); Scanner scanner = new Scanner(System.in); initializeGame(); while (true) { showBoard(); <중략> System.out.println("잘못된 번호를 선택하셨습니다."); } } } private static boolean isLandMineCell(int selectedRowIndex, int selectedColIndex) { return LAND_MINES[selectedRowIndex][selectedColIndex]; } private static boolean doesUserChooseToOpenCell(String userActionInput) { return userActionInput.equals("1"); } <중략> private static int convertColFrom(char cellInputCol) { switch (cellInputCol) { case 'a': return 0; case 'b': return 1; case 'c': return 2; case 'd': return 3; <중략> } } private static void showBoard() { System.out.println(" a b c d e f g h i j"); for (int row = 0; row < BOARD_ROW_SIZE; row++) { System.out.printf("%d ", row + 1); for (int col 이렇게 추상화 원칙을 적용한 지뢰찾기 프로그램 리펙터링 1차를 마쳤다. 생각보다 1줄짜리 코드도 메서드로 빼거나 단 두곳 정도에서만 공통으로 쓰인값도 상수로 빼는 경우도 있었다. 추상화는 코드의 양이 중요한 것이 아니다.의미를 부여할 수 있다면 코드 수는 중요하지 않다. 섹션 3 - 논리 사고의 흐름 인지적 경제성이란,인지 및 지각은 처리과정에서 사용되는 에너지(비용)와 이에 따른 경제적 결과(효율)을고려한다는 이론이다. 갑자기 뜬구름같은 말일 수도 있겠지만, 우리가 코드를 해석할 때이 인지적 경제성은 굉장히 중요한 요소가 된다. 클린하지 않은 코드는 읽어 내려갈 때,이전 내용의 코드를 계속해서 기억해야하는 경우가 많다. 클린코드로 리펙터링하기 1편에서 특정 복잡한 로직들을추상화하여 메서드로 분리하지않았다면 게임에 대한 전체적인 코드가 담긴 main메서드를 읽어내려갈 때,중간중간 자잘한 데이터타입 변환, 값 계산 등의 부분들이 등장하면현재가 게임 내 어떤 플로우에 위치해 있는지, 이전 중요한 플로우는 무엇이었는지계속해서 기억을 쌓아가면서 읽어내야한다. 하지만, 굳이 구체할 필요 없는 부분들은 메서드 몸체 내부에 구현해주고,메서드명만으로 해당 작업이 어떤 작업인지 추상함으로써 main 메서드를 읽는 사람으로 하여금 전체적인 플로우를 단숨에 파악하고,그 중 한 부분만 집중해서 보고 싶다면 메서드 몸체 부분을 분석하면되도록 하였다. 우리가 어플리케이션을 개발할 때, 사용하지 않는 데이터들은 바로바로 메모리에서 비워주고이를 가능케 하도록 객체들간의 의존성을 최대한 줄이는 것도이 인지적 경제성과 밀접한 관계가 있다고 볼 수 있다. 우리 뇌도 기억하고 있어야할 요소가 적을수록, 인지해야할 구체적인 부분들은 숨겨져 있어추상적인 부분들만 파악하면 될 때, 더욱 적은 자원을 사용한다. 오늘은 이 인지적 경제성 원리에 맞춰, 최소한의 인지만으로도 코드를 해석할 수 있도록지뢰찾기 게임 시스템을 리펙터링해고자 한다. 인지적 경제성을 향상시킬 수 있는 전략들을 알아보면서, 동시에 리펙터링을 이어나가자보자. Early return여러 조건들이 나열된 if문에서 return과 같이 끊어주는 구간이 없으면이전 조건들의 정보들을 계속 기억에 유지하면서 (뇌 메모리 공간에 올려 놓은 상태로)아래 코드들을 읽게 된다. 이때, 이 조건문 부분을 메서드로 분리한 다음 각 조건에 return으로 탈출 구간을 만들어주면, 코드를 읽을 때return으로 끝난 구현부는 기억에 유지하지않으면서 다음 부분들을 읽어내려갈 수 있게된다. 큰 차이가 없어 보인다고 느낄 수 없지만, 조건 분기가 복잡해질수록이 전략은 굉장한 효과를 보인다. 특히, else는 지양하는 편이 좋다. 상황에 따라 다르겠지만 대부분 상황에서else는 이전 조건들을 모두 기억하고 이 조건들에 해당하지 않는 경우들을분석해내야하는 과정을 만들어 내기 때문이다.같은 원리로 switch/case문도 지양하는 편을 추천한다. String cellInput = getCellInputFromUser("선택할 좌표를 입력하세요. (예: a1)", scanner); String userActionInput = getUserActionInputFromUser("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)", scanner); int selectedColIndex = getSelectedColIndex(cellInput); int selectedRowIndex = getSelectedRowIndex(cellInput); if (doesUserChooseToPlantFlag(userActionInput)) { board[selectedRowIndex][selectedColIndex] = FLAG_SIGN; checkIfGameIsOver(); } else if (doesUserChooseToOpenCell(userActionInput)) { if (landMines[selectedRowIndex][selectedColIndex]) { board[selectedRowIndex][selectedColIndex] = LAND_MINE_SIGN; changeGameStatusToLose(); continue; } else { open(selectedRowIndex, selectedColIndex); } checkIfGameIsOver(); } else { System.out.println("잘못된 번호를 선택하셨습니다."); } } }지뢰찾기에서 이런 부분이 있었다. 조건문 분기가 한 눈에 들어오지않을 뿐더러,여러 조건들을 비교하면서 각 상황의 차이가 어떻게 다른지,또 조건문내에서도 depth가 나뉘면서 안쪽에 조건문은 바깥쪽의 조건에 대한 기억을유지한 상태로 읽게된다. 해당 조건문들의 나열은 크게봤을 때결국 유저가 지뢰 탐색을 선택했는지, 깃발 꽂기를 선택했는지에 대한분기이다. 해당 작업을 메서드로 분리한다음이 두가지 선택사항이 명확하게 분리되고, 각 상황을 살펴볼 때 반대쪽 상황의 코드는 잊어도되도록 구현해보자. String cellInput = getCellInputFromUser("선택할 좌표를 입력하세요. (예: a1)", scanner); String userActionInput = getUserActionInputFromUser("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)", scanner); actOnCell(cellInput, userActionInput); } } private static void actOnCell(String cellInput, String userActionInput) { int selectedColIndex = getSelectedColIndex(cellInput); int selectedRowIndex = getSelectedRowIndex(cellInput); if (doesUserChooseToPlantFlag(userActionInput)) { board[selectedRowIndex][selectedColIndex] = FLAG_SIGN; checkIfGameIsOver(); return; } if (doesUserChooseToOpenCell(userActionInput)) { if (landMines[selectedRowIndex][selectedColIndex]) { board[selectedRowIndex][selectedColIndex] = LAND_MINE_SIGN; changeGameStatusToLose(); return; } open(selectedRowIndex, selectedColIndex); checkIfGameIsOver(); return; } System.out.println("잘못된 번호를 선택하셨습니다."); }이렇게 함으로써, 지뢰 탐색과 깃발 꽂기의 두 경우가 나뉘고,return을 통해 머리 속에 기억해야할 정보들의 끊김 포인트를 정해주어코드를 읽어내림에 있어 부담을 줄여줬다. 사고의 Depth 줄이기위에서도 잠깐 언급된 전략이기도 한데,분기문이나 반복문에서 중첩되어 있는 부분은 머리에 연속적인 기억을 남게한다.이를 최대한 분리하고 추상화함으로써 코드의 가독성을 향상시킬 수 있다. private static boolean isAllCellIsOpened() { boolean isAllOpened = true; for (int row = 0; row < BOARD_ROW_SIZE; row++) { for (int col = 0; col < BOARD_COL_SIZE; col++) { if (BOARD[row][col].equals(CLOSED_CELL_SIGN)) { isAllOpened = false; } } } return isAllOpened; }기존 코드에 게임 승리조건을 확인하기 위한 메서드가 있었다.2차원 배열을 중첩 반복문을 통해 탐색하는 로직인데,현재는 매우 간단한 상황이라 큰 상관은 없겠지만 어쨌든불필요한 Depth가 나뉘고 있다. private static boolean isAllCellIsOpened() { return Arrays.stream(BOARD) //Stream<String[]> .flatMap(Arrays::stream) //Stream<String> .noneMatch(cell -> CLOSED_CELL_SIGN.equals(cell)); }Stream을 활용하여 간단하게 작성해준다.중첩반복문이 직관적으로 쉽게 해석할 수는 있으나, Stream문법만 안다면똑같이 쉽게 이해할 수 있으므로, 보다 가독성이 좋은 위에 방식을 사용했다. 위 예시는 간단한 로직이라 전과 후 큰 차이가 없어 보인다.다음 예시를 보자private static void initializeGame() { for (int row = 0; row< BOARD_ROW_SIZE; row++) { for (int col = 0; col < BOARD_COL_SIZE; col++) { BOARD[row][col] = CLOSED_CELL_SIGN; } } for (int i = 0; i < BOARD_COL_SIZE; i++) { int col = new Random().nextInt(BOARD_COL_SIZE); int row = new Random().nextInt(BOARD_ROW_SIZE); LAND_MINES[row][col] = true; } for (int row = 0; row < BOARD_ROW_SIZE; row++) { for (int col = 0; col < BOARD_COL_SIZE; col++) { int count = 0; if (!isLandMineCell (row, col)) { if (row - 1 >= 0 && col - 1 >= 0 && isLandMineCell(row - 1, col - 1)) { count++; } <중략> LAND_MINE_COUNTS[row][col] = count; continue; } LAND_MINE_COUNTS[row][col] = 0; } } }처음 게임이 로드될 때, 각 타일마다 주변 지뢰 개수를 세서 기록하는 부분이다.중첩 반복문에 중첩 조건문까지 Depth가 굉장히 많이 나뉜 모습이다.추상화하여 메서드로 분리할 수 있는 부분이 있는지 살펴보자.if (row - 1 >= 0 && col - 1 >= 0 && isLandMineCell(row - 1, col - 1)) { count++; } if (row - 1 >= 0 && isLandMineCell(row - 1, col)) { count++; } if (row - 1 >= 0 && col + 1 < BOARD_COL_SIZE && isLandMineCell(row - 1, col + 1)) { count++; <중략> } if (row + 1 < BOARD_ROW_SIZE && col + 1 < BOARD_COL_SIZE && isLandMineCell(row + 1, col + 1)) { count++; } LAND_MINE_COUNTS[row][col] = count;해당 작업은 지뢰가 아닌 타일의 주변 타일에서 지뢰의 개수가 몇개인지 세는 구체적인작업을 표현한다. '근처의 지뢰를 세는 작업'으로 추상화하여 메서드로 분리해준다. for (int row = 0; row < BOARD_ROW_SIZE; row++) { for (int col = 0; col < BOARD_COL_SIZE; col++) { int count = 0; if (!isLandMineCell (row, col)) { count = countNearbyMines(row, col, count); LAND_MINE_COUNTS[row][col] = count; continue; } LAND_MINE_COUNTS[row][col] = 0; } } }외부 메서드와의 경계가 생긴대신 기존 코드 부분은 굉장히 간결해졌다.private static int countNearbyMines(int row, int col, int count) { if (row - 1 >= 0 && col - 1 >= 0 && isLandMineCell(row - 1, col - 1)) { count++; } if (row - 1 >= 0 && isLandMineCell(row - 1, col)) { count++; } if (row - 1 >= 0 && col + 1 < BOARD_COL_SIZE && isLandMineCell(row - 1, col + 1)) { count++; } <중략> } if (row + 1 < BOARD_ROW_SIZE && col + 1 < BOARD_COL_SIZE && isLandMineCell(row + 1, col + 1)) { count++; } return count; }추상화된 메서드명에 적합한 구체적인 작업이 메서드 몸체 부분에 구현되어있다. 공백 라인 활용모든 사람들이 무의식적으로 가장 많이 사용하고 있는 전략일 것이다.중첩 조건문을 메서드로 분리한 뒤 중간중간 return을 달아주었던 이유와 동일하게 복잡하게 늘어져 있는 코드 중간중간에 공백을 통해 관심사를 분리함으로써각 코드 덩어리 부분이 어떤 흐름의 차이점을 보이는지 명시하는 것이다. 코드를 쭉 읽어내려가다가 이전 정보들을 잠시 기억에서 내려놓을브레이크 지점을 주는 것이다. public static void main(String[] args) { showGameStartComments(); initializeGame(); while (true) { try { showBoard(); if (doesUserWinTheGame()) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } if (doesUserLoseTheGame()) { System.out.println("지뢰를 밟았습니다. GAME OVER!"); break; . . . (중략) } }기존 main 메서드이다. 각 역할을 관심사로 분리된 작업들이메서드로 분리되고 추상화된 메서드명만 남아서 굉장히 깔끔해진 상태이다. 여기서 추가로 구분되는 작업들에 공백을 준다.예를 들어showGameStartComments(); initializeGame();이 두 작업은 게임이 로드될 때 초기 세팅이 되는 부분이고,while문은 유저의 행동에 따라 게임이 계속해서 진행되는 부분이다. 공백을 통해가볍게 분리해준다. 이 전략은 너무 흔하고 모두 의식하지않아도 코드를 깔끔하게 정리할 때 사용했을것 같으므로 여기까지만 설명한다. 부정어 지양하기평소에 코드를 작성할 때 '!' 연산을 굉장히 애용했다.코테의 영향일지는 몰라도, 단어 하나만으로 조건문과 같은 상황에서조건을 쉽고 편하게 작성할 수 있다는 장점이 있어서 자주 사용했다. 하지만 이러한 부정연산은 !이 붙은 값이나 메서드를 먼저 이해하고 나서야 기존 조건인반대 상황을 떠올릴 수 있다는 인지적 경세성을 해치는 과정이 필요하며,한 글자라 로직에 끼치는 영향력에 큰 것에 비해 인지는쉽게 되지않는다는 단점이 존재한다. 때문에 ! 표현은 최대한 지양하고, 부정해야하는 대상이 값이었다면해당 값의 반대 의미를 가진 변수를 만들어 사용하거나 메서드였다면 반대의 논리형 값을 반환하는 메서드를 만들어 사용하는 편이더 좋은 방향일 것이다. for (int row = 0; row < BOARD_ROW_SIZE; row++) { for (int col = 0; col < BOARD_COL_SIZE; col++) { int count = 0; if (!isLandMineCell (row, col)) { count = countNearbyMines(row, col, count); LAND_MINE_COUNTS[row][col] = count; continue; } LAND_MINE_COUNTS[row][col] = 0; } }기존 initializeGame메서드에 이 ! 연산이 쓰이는 곳이 있었다.지뢰가 있는 타일이 아니라면 주변 지뢰의 개수를 세는 메서드를 호출한다.이 코드를 읽을 때에는 먼저 이 메서드 명을 읽고 '아 이건 현재 조회하는셀리 지뢰인지를 확인하는 메서드이구나'를 인지한 뒤 반대 상황인 '현재 조회 타일이 지뢰인 상황'을 떠올려야한다.이 과정이 쉽다고는 해도 !연산은 눈에 잘 안 보인다는 단점도 존재했다.어떻게 개선할 수 있을까? if문 분기 상황을 역전시켜서 isLandMineCell메서드가 참인 경우를 사용하도록해보자.for (int row = 0; row < BOARD_ROW_SIZE; row++) { for (int col = 0; col < BOARD_COL_SIZE; col++) { if (isLandMineCell(row, col)) { //if문 분기를 조건을 역전시켜서 부정연산자를 사용하지않는 방향으로 바꿔줌 LAND_MINE_COUNTS[row][col] = 0; continue; } int count = countNearbyMines(row, col); LAND_MINE_COUNTS[row][col] = count; } }이렇게 하면, 메서드명만 보고 해당 조건문 아래 내용이 어떤 상황에 적용되는지,이 조건문 밖에 상황은 어떤 상황인지 명확하게 구분될 뿐 아니라 가독성이더 좋아졌다. 물론 이 !연산도 무조건적으로 사용하지말라는 것은 아니고,현재 부정표현이 꼭 이 방법밖에 없는 방향인지,보다 더 가독성이나 재사용성을 높일 수 있는 방법은 없는지 고민해보는 과정이중요하다.마지막으로 오늘 배웠던 것들을 복습하기 위해public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; }이 코드를 리펙터링해보자.if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; }주문의 아이템 목록이 존재하는지 검사한다. 역할이 명확하고'아이템 존재 여부 판단'이라는 메서드명으로 추상화할 수 있으므로 메서드로 분리해준다.public boolean isEmptyItemIn (Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return true; } return false; }이렇게 분리할 수 있을 것이다. 메서드명과 파라미터가 이어져서 메서드의 구체 작업을보다 쉽게 유추할 수 있도록 하였다.public boolean validateOrder(Order order) { if (isEmptyItemIn(order)) return false; if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } return true; }반복문 분기를 한번 끊어주게 되면서, 인지적 경제성을 저해하는 else문이하나 사라지고 조건문의 depth도 한 단계 줄이는 효과를 보였다.만약, validateOrder메서드에서 아이템 목록 존재 여부에 따른작업 분기만 관심 있는 사람이 현재 코드를 읽는다면 이전보다 굉장히 향상된인지적 경제성을 가지게 될 것이다. 현재 코드에서 가장 바깥쪽 경계인 조건문 분기인 if와else if를 살펴봤을 때, 주문의 가격이 유효한지 유효하지 않은 지 나눠주는 부분이 있다.유효한 경우에는 또 다시 조건문 분기가 일어나므로, 우선적으로 유요하지않았을 때 false만 반환하는 부분을 메서드로리하여 가독성을 높여보자.이때, '!'연산을 지양하고자 올바르지 않은 가격임을 확인하는 메소드로 구현한다.public boolean validateOrder(Order order) { if (isEmptyItemIn(order)) return false; if(hasInvalidTotalPriceIn(Order order) return false; if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } return true; } public boolean isEmptyItemIn (Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return true; } return false; } public boolean hasInvalidTotalPriceIn (Order order) { if(order.getTotalPrice() < 0) { log.info("올바르지 않은 총 가격입니다."); return true; } return false; } !연산을 하나 줄이고, 조건문의 depth도 줄였다.마지막 조건문 분기를 확인해보면사용자 정보의 유효성을 검사한다. 역시 메서드로 분리하면서 !연산도 없애보자.public boolean validateOrder(Order order) { if (isItemEmptyOf(order)) return false; if (hasInvalidTotalPriceIn(order)) return false; if (hasMissingCustomerInfoIn(order)) return false; return true; } <중략> public boolean hasMissingCustomerInfoIn (Order order) { if (order.hasCustomerInfo()) return false; log.info("사용자 정보가 없습니다."); return true; } 이렇게 개선될 수 있다. 마지막으로 로그와 공백 줄맞춤 등을 정리해보자public boolean validateOrder(Order order) { if (isItemEmptyOf(order)) { log.info("주문 항목이 없습니다."); return false; } if (hasInvalidTotalPriceIn(order)) { log.info("올바르지 않은 총 가격입니다."); return false; } if (hasMissingCustomerInfoIn(order)) { log.info("사용자 정보가 없습니다."); return false; } return true; } public boolean isItemEmptyOf(Order order) { return order.getItems().isEmpty(); } public boolean hasInvalidTotalPriceIn(Order order) { return order.getTotalPrice() < 0; } public boolean hasMissingCustomerInfoIn(Order order) { return !order.hasCustomerInfo(); }추상화하여 분리된 메서드들 중 hasMissingCustomerInfoIn 메서드는위에서 권장한 것과 다르게 '!'연산을 사용했는데,public boolean hasMissingCustomerInfoIn(Order order) { if (order.hasCustomerInfo()) return false; return true; }이렇게 작성하면 다른 메서드들과의 통일성이 좀 깨지기도 하고'!'연산이 쓰이는 곳이 구체적인 메서드 몸체 내부이기 때문에부정 연산자를 사용하는 방향으로 했다. 가장 추상화 단계가 높은 validateOrder메서드의유효성 검사 방식과 플로우가 보기 좋게 작성되었다. 총 3가지의 검사를 거치고 모두 통과했을 때에야 true를 반환한다.각 검사는 메서드 명과 파라미터의 조합으로 어떤 작업을 하는지명확하게 추상화되었다. 클린 코드에는 적절한 예외 처리도 굉장히 중요하다.무분별한 예외 처리는 가독성을 저해하고,명확하고 직관적으로 작성한 예외 처리는 각 코드들이어떤 동작을 기대하고, 어떤 상황을 기획적인 측면에서 방지하고자 하는지알 수 있기 때문이다. '해피 케이스'는 소프트웨어 개발에서 모든 조건이 정상적으로충족되고 시스템이 의도한대로 작동하는 이상적인 상황을 의미한다. 이 해피 케이스는 시스템의 기대 동작을 정의하며,기본적인 흐름을 중심으로 설계하고 개발하는 출발점이 된다. 하지만 정말 완벽하게 설계했다고 생각한 시스템에서도,실제 운영 환경에서는 이 해피 케이스에 반하는 상황들이 발생하기 때문에예외 처리가 필요해진다. 결국 예외 처리도, 이 해피 케이스를 기반으로 이 케이스들에서 벗어나는행위들을 정의함으로써 헨들링이 시작된다. 지뢰게임 시스템으로 살펴보자while (true) { showBoard(); //화면에 보드를 띄우고 게임이 진행되니 여기 공백으로 구분해주기 if (doesUserWinTheGame()) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } if (doesUserLoseTheGame()) { System.out.println("지뢰를 밟았습니다. GAME OVER!"); break; } String cellInput = getCellInputFromUser(); String userActionInput = getUserActionInputFromUser(); actOnCell(cellInput, userActionInput); } 기존 시스템에서 이 while문 안이 실질적으로 유저의 입력을 받아 게임을이어나가는 곳이다. 컴파일 과정이 아닌 런타임 내에 에러가 난 다면 이 반복문에서 발생할확률이 클 것이다. 이 부분에 적절한 예외처리를 적용해보자. 매 게임이 진행될때마다 getCellInputFromUser와 getUserActionInputFromUser을통해 어느 행 어느 열 위치 cell에서 지뢰 탐색을 할지 입력을 받는다. 이 상황에서 해피케이스는 유저가 게임판 행열 크기 내에 cell만 입력해주는 것이다.public static final int BOARD_ROW_SIZE = 8; public static final int BOARD_COL_SIZE = 10;그렇다면 이 범위 내에 값을 입력하지 않거나 지정된 값 타입을 작성하지 않는다면기획적인 측면의 장애 발생이다. 조회하는 배열의 범위를 벗어나므로, 에러가 터지겠지만해당 에러는 시스템적인 장애라기 보다는 내 서비스 기획내에서 벗어난 행위이며프로그램이 중단되는 일을 방지해야 하기 때문에,적절한 예외처리를 하여 유저로부터 입력형태가잘못됐음을 인터페이스로 응답해주어야한다. 이처럼 서비스 설계와 위반되는 유저의 행위로 인해 발생하는 에러와개발자가 예상치 못한 시스템적인 장애를 구분하기 위해서비스에러는 RuntimeException을 상속받아 따로 구현해준다.//개발자가 의도한, 예상하는 에러 public class AppException extends RuntimeException { public AppException(String message) { super(message); } }그렇다면 이제 유저의 입력을 받는 부분에서private static int convertRowFrom(char cellInputRow) { int rowIndex = Character.getNumericValue(cellInputRow) -1; if(rowIndex >= BOARD_ROW_SIZE) { throw new AppException("잘못된 입력입니다"); } return rowIndex; }이렇게 해피 케이스인 rowIndex >= BOARD_ROW_SIZE에 위반됐을 때해당 에러를 발생시키도록한다.while (true) { try { showBoard(); //화면에 보드를 띄우고 게임이 진행되니 여기 공백으로 구분해주기 if (doesUserWinTheGame()) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } if (doesUserLoseTheGame()) { System.out.println("지뢰를 밟았습니다. GAME OVER!"); break; } //Scanner scanner = new Scanner(System.in); //사용하는 쪽과 가깝게 두기 근데 이러면 반복문 돌 때 마다 새로 다시 생성하게됨. 상수로 빼주기 //게임 종료 조건 체크 후 셀을 어떻게 할지 결정하므로 여기 공백 라인으로 구분 String cellInput = getCellInputFromUser(); String userActionInput = getUserActionInputFromUser(); actOnCell(cellInput, userActionInput); } catch (AppException e) { System.out.println(e.getMessage()); } } }그럼 반복문은 이렇게 바뀌게 될 것이다.일반적인 서비스라면 특정 에러 코드를 웹서버로 내려주고웹서버는 해당 코드에 맞는 경고창과 같은 인터페이스 변화를 유저에게 보여줄 것이다. 이렇게 최대한 예상될 수 있는 에러들을 생각해내서 헨들링할 수 있어야한다.QA와 테스트 코드가 중요한 이유이기도 하다. 사전에 헨들링할 수 있는에러들을 모두 명시하고 예외 처리하여 속된말로 서버가 터지는(WAS가 꺼지는)일이 없도록 해야되기 때문이다. 하지만 개발자가 모든 예상을 할 수 있는 것은 아니기에,이러한 에러를 잡을 수도 있도록 한다.while (true) { try { showBoard(); //화면에 보드를 띄우고 게임이 진행되니 여기 공백으로 구분해주기 if (doesUserWinTheGame()) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } if (doesUserLoseTheGame()) { System.out.println("지뢰를 밟았습니다. GAME OVER!"); break; } //Scanner scanner = new Scanner(System.in); //사용하는 쪽과 가깝게 두기 근데 이러면 반복문 돌 때 마다 새로 다시 생성하게됨. 상수로 빼주기 //게임 종료 조건 체크 후 셀을 어떻게 할지 결정하므로 여기 공백 라인으로 구분 String cellInput = getCellInputFromUser(); String userActionInput = getUserActionInputFromUser(); actOnCell(cellInput, userActionInput); } catch (AppException e) { System.out.println(e.getMessage()); } catch (Exception e) { System.out.println("예상치 못한 프로그램의 문제가 발생했습니다"); //개발자가 의도치않은 시스템 장애 발생1 } } }이렇게 예상할 수 없는 에러가 발생했을 때에 대한 대응도코드단에서 대응할 수 있어야한다. AppException이 잘 작동하는지 확인해보자잘 작동하는 모습을 보인다.이번엔 일부러 시스템 장애를 일으켜 Exception에 대한 예외처리가잘 작동하는지 확인해보자.역시 잘 작동한다. 현재는 간단한 게임 서비스라 복잡하게 생각할 것이 없지만,예외 처리는 서비스가 복잡해질수록 가장 어려우면서 동시에중요한 과정이기도하다. 섹션 4 - 객체 지향 패러다임 체지향 프로그래밍 설계에서 정말 중요한 SOLID 원칙에 대해서 알아보겠다.기술면접으로 자주 나오는 용어이기도 하고, 전공 수업에서도 다루는 중요한 개념이다 보니다들 뭔지는 알지만 사실 본인 프로젝트에 적용하기란 쉽지 않다. 개념을 확실하게 익혀 기존 프로젝트를 리펙터링하거나 추후 프로젝트에서는설계 단계에서부터 이 원칙을 잘 적용해보고자 개념을 정리해본다.또한, 간단한 코드로 각 원칙을 적용해 구현해보면서 활용법을 익혀본다. SOLID 원칙은 객체 지향 설계의 기초를 이루며, 이를 잘 적용하면 소프트웨어의 유지보수성과 확장성을 크게 향상시킬 수 있다.SOLID는 5개의 원칙의 각 앞글자를 따서 만든 용어이다. SRP (Single Responsibility Principle)단일 책임 원리단일 책임 원칙이다.단일 책임 원칙은 클래스는 단 하나의 책임만을 가져야 하며,그 책임을 완전히 캡슐화해야 한다는 원칙이다. 즉, 클래스는 변경의 이유가 단 하나뿐이어야 한다.이렇게 함으로써 클래스의 응집도가 높아지고,클래스 간의 의존도는 낮아지도록 할 수 있다. 클래스 간의 영향 범위가 줄어들수록(한 곳의 변경에 따른 다른 곳의 변화가 적을수록)유지보수는 쉬워진다. 우선, 이런 원칙을 위반한 상황을 살펴보자.// User.java public class User { private String name; private String email; // 생성자, getter, setter 생략 public void save() { // 데이터베이스에 사용자 정보 저장 } public void sendEmail(String message) { // 이메일 전송 로직 } }이 클래스는 이메일 전송과 데이터 저장이라는 두가지 역할을 하고 있다.이렇게 한다면, 추후 데이터 저장 방식이 서비스 측면에서 변경되었을 때이 User 클래스를 변경해야하고, 같은 모듈에 존재하는 이메일 전송 기능에어떤 영향을 주게될지 알 수 없다. 그리고 무엇보다 User라는 이름으로 추상화된 클래스에게 데이터 저장과이메일 전송이라는 구체 기능은 논리적으로 어울리지 않는다 만약,// User.java public class User { private String name; private String email; // 생성자, getter, setter 생략 }// UserRepository.java public class UserRepository { public void save(User user) { // 데이터베이스에 사용자 정보 저장 } }// EmailService.java public class EmailService { public void sendEmail(String email, String message) { // 이메일 전송 로직 } }이렇게 세가지의 객체로 나눈다면,각 클래스명은 각자 가진 구체적인 기능에 대한 추상을 적절한 이름으로 보이게 되고,각 클래스가 본인 이름에 맞는 하나의 책임만을 가지게된다. 각 클래스는 서로가 서로의 추상화된 모습만 알게되었다.이렇게 함으로싸 각 기능의 변경이 다른 부분에 미치는 영향을 최소화되었다. 이메일 전송 방식을 변경하고 싶으면 개발자는 EmailService에만 접근하고,데이터 저장 방식을 변경하고 싶으면 UserRepository에,서비스 User의 정의를 다시 하고 싶으면 User 클래스에만 접근하면 된다. OCP (Open-Closed Principle)개방 폐쇄의 원칙개방/폐쇄 원칙은 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙이다. 즉, 기존 코드를 수정하지 않고도 시스템의 기능을 확장 할 수 있어야 한다는 의미이다.// Shape.java public class Shape { public String type; public double width; public double height; public double radius; } // AreaCalculator.java public class AreaCalculator { public double calculateArea(Shape shape) { switch (shape.type) { case "Rectangle": return shape.width * shape.height; case "Circle": return Math.PI * shape.radius * shape.radius; default: throw new IllegalArgumentException("Unknown shape type"); } } }위 상황에서는 새로운 도형을 추가 할 때마다 AreaCalculator 클래스를 수정해야하는문제점이 발생한다.// Shape.java public interface Shape { double calculateArea(); } // Circle.java public class Circle implements Shape { private double radius; // 생성자, getter, setter 생략 @Override public double calculateArea() { return Math.PI * radius * radius; } } // AreaCalculator.java public class AreaCalculator { public double calculateArea(Shape shape) { return shape.calculateArea(); } }하지만 위와 같이 객체 지향의 추상화와 다형성의 장점을 살려 설계한다면,기존 코드의 변경 없이 도형이 추가되는 시스템의 기능 확장을유연하게 대처할 수 있게된다.객체간의 협력은 추상화된 공개 메서드를 통해 소통이 이뤄지도록 해야추후 구체에 해당하는 로직 변경이 순조로워지는 것이다. LSP : Liskov Substitution Principle리스코프 치환 원칙이라고 한다.부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야한다는 원칙이다.즉, 프로그램의 정확성을 유지하면서 부모 타입의 객체를 자식 타입의 객체로 대체할 수 있어야 한다는 것을 의미한다. 이를 통해 상속 관계에서의 올바른 다형성을 보장한다.// Bird.java public class Bird { public void fly() { // 날기 로직 } } // Penguin.java public class Penguin extends Bird { @Override public void fly() { throw new UnsupportedOperationException("펭귄은 날 수 없습니다."); } }이렇게 Bird를 상속했지만 리스코프 치환 원칙을 위배하여, 기존 부모 클래스의역할을 수행하지 못하는 자식 클래스 Penguin가 있다면Bird의 의존을 받는 곳에서 기존 Bird의 타입을 Penguin으로 바꿨을 때,fly()가 호출되는 지점에서 에러가 발생하게 될 것이다. // Bird.java public abstract class Bird { // 공통된 기능 } // FlyingBird.java public interface FlyingBird { void fly(); } // Sparrow.java public class Sparrow extends Bird implements FlyingBird { @Override public void fly() { // 참새 날기 로직 } } // Penguin.java public class Penguin extends Bird { // 펭귄은 날지 않으므로 FlyingBird를 구현하지 않음 }이렇게 한다면, FlyingBird 인터페이스를 구현한 클래스만fly 메서드를 가지므로, FlyingBird의 구현체가 역할하고 있엇던 곳은동일하게 FlyingBird의 다른 구현체로만 변경되도록하고 부모 클래스인 Bird가 쓰이는 곳은 이 부모 클래스를 상속받는자식 클래스라면 어떤 타입이라도 변경될 수 있도록 한다. ISP : Interface Segregation Principle인터페이스 분리 원칙인터페이스 분리 원칙은 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다. 즉, 하나의 일반적인 인터페이스보다는 여러 개의 구체적인 인터페이스가더 낫다는 의미한다. 이를 통해 인터페이스가 클라이언트의 필요에 맞게 세분화되어,불필요한 의존성을 줄일 수 있다.// Worker.java public interface Worker { void work(); void eat(); } // HumanWorker.java public class HumanWorker implements Worker { @Override public void work() { // 일하기 로직 } @Override public void eat() { // 식사하기 로직 } } // RobotWorker.java public class RobotWorker implements Worker { @Override public void work() { // 일하기 로직 } @Override public void eat() { throw new UnsupportedOperationException("로봇은 식사할 필요가 없습니다."); } }위와 같은 상황이 있다고 하자.Worker 인터페이스가 여러 역할을 하나로 묶고 있다.하지만 Worker의 구현체들 중에 Worker의 메서드가 필요없는 클래스가 보인다// Workable.java public interface Workable { void work(); } // Eatable.java public interface Eatable { void eat(); } // HumanWorker.java public class HumanWorker implements Workable, Eatable { @Override public void work() { // 일하기 로직 } @Override public void eat() { // 식사하기 로직 } } // RobotWorker.java public class RobotWorker implements Workable { @Override public void work() { // 일하기 로직 } }위와 같이 인터페이스를 더욱 세분화함으로써,불필요한 의존성들을 제거해준다.DIP : Dependency Inversion Principle의존성 역전 원칙존성 역전 원칙은 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다는 원칙이다. 또한, 추상화는 세부 사항에 의존해서는 안 되고, 세부 사항이 추상화에 의존해야 한다는 의미도 포함한다. 이를 통해 모듈 간의 결합도를 낮추고, 유연성과 재사용성을 높일 수 있다.// LightBulb.java public class LightBulb { public void turnOn() { // 전등 켜기 로직 } public void turnOff() { // 전등 끄기 로직 } } // Switch.java public class Switch { private LightBulb lightBulb; public Switch(LightBulb lightBulb) { this.lightBulb = lightBulb; } public void operate() { // 스위치 작동 시 전등 켜기 또는 끄기 lightBulb.turnOn(); } }Switch 클래스가 LightBulb에 직접적으로 의존하고 있어, 다른전등으로 변경 시 Switch 클래스를 수정해야한다.// Switchable.java public interface Switchable { void turnOn(); void turnOff(); } // LightBulb.java public class LightBulb implements Switchable { @Override public void turnOn() { // 전등 켜기 로직 } @Override public void turnOff() { // 전등 끄기 로직 } } // Fan.java public class Fan implements Switchable { @Override public void turnOn() { // 팬 켜기 로직 } @Override public void turnOff() { // 팬 끄기 로직 } } // Switch.java public class Switch { private Switchable device; public Switch(Switchable device) { this.device = device; } public void operate() { // 스위치 작동 시 장치 켜기 또는 끄기 device.turnOn(); } }이제 Switch 클래스는 Switchable 인터페이스에 의존하므로, 새로운 장치가 추가되더라도 Switch 클래스를 수정할 필요가 없다. Switchable의 구현체라면,새로운 장치로 변경된다 해도 기존 시스템(보다 추상레벨이 높은 로직 위치)은문제없이 작동할 것이다. 각 원칙의 개념을 명확하게 이해하는 것도 중요하지만,이 이해를 바탕으로 프로젝트의 설계를 어떻게 진행해나가는지에 대한실무 역량도 중요한 것 같다.  섹션 5 - 객체 지향 적용하기해당 섹션은 아직 충분한 회고와 복습이 이뤄지지않아, 강의 내용에서 나온 개념들 중에 추가로 공부한 내용들을 정리하려고 한다.하루이틀 정도 지뢰게임 프로젝트를 통해 다시 복습해보면서 익혀야할 것 같다. 슬슬 이제 내가 모르는 개념들이 수업에 많이 나오는 것 같다. 상속과 조합, 값 객체(Value Object), 일급 시민, Enum 활용, 그리고 다형성을 통해 반복되는 조건문을 제거하며 OCP를 지키는 방법 등을 다룰 것이다. 각 개념을 이해하고 코드 예제를 통해 어떻게 적용할 수 있는지 살펴보자. 상속과 조합상속은 코드 재사용 측면에서 유용하지만, 구조가 시멘트처럼 굳어져 수정이 어려워진다는 단점이 있다. 특히 부모와 자식 클래스 간의 결합도가 높아지면 유지보수에 큰 문제를 일으킬 수 있다. 즉, 부모 클래스가 변경되면 자식 클래스에도 영향을 미치게 되는 것이다.이렇게 부모-자식의 관계는 한번 맺어지면 종속적인 형태를 띄기 때문에, 초기 관계를 맺을 때도 타당성을 꼭 점검해봐야한다. 조합과 인터페이스를 활용하면 유연한 구조를 만들 수 있다. 상속을 통한 코드의 중복 제거가 주는 이점보다, 중복이 생기더라도 유연한 구조를 설계하는 것이 장기적으로 훨씬 더 좋다. 코드로 한번 이해해보자// 상속을 사용하는 경우 class Animal { void sound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override void sound() { System.out.println("Bark"); } } // 조합과 인터페이스를 사용하는 경우 interface SoundBehavior { void makeSound(); } class BarkSound implements SoundBehavior { public void makeSound() { System.out.println("Bark"); } } class Animal { private SoundBehavior soundBehavior; public Animal(SoundBehavior soundBehavior) { this.soundBehavior = soundBehavior; } void performSound() { soundBehavior.makeSound(); } } class Dog extends Animal { public Dog() { super(new BarkSound()); } }위 예시에서 상속을 사용한 방식은 Dog 클래스가 Animal 클래스에 강하게 의존하게 되어 있다. 하지만 조합을 사용하면 SoundBehavior 인터페이스를 통해 더 많은 유연성을 확보할 수 있다. Value Object: 도메인을 추상화한 값 객체값 객체(Value Object)는 도메인의 개념을 값으로 추상화하여 표현하는 객체이다. 중요한 점은 값 객체는 불변성, 동등성, 유효성 검증 등을 보장해야 한다는 것이다. 이를 통해 값 객체는 한 번 생성되면 변경되지 않고, 내부의 값이 같으면 같은 객체로 취급된다. • 불변성: 값을 안전하게 유지하기 위해 불변 객체로 설계하는 것이 중요하다. 예를 들어, 필드는 final로 선언하고 setter 메서드는 제공하지 않아야 한다.• 동등성: 서로 다른 인스턴스라도 내부의 값이 같다면 같은 값 객체로 취급한다. 이를 위해 equals()와 hashCode() 메서드를 재정의해야 한다.• 유효성 검증: 객체 생성 시점에 값이 유효한지 확인해야 한다. 이렇게 하면 객체의 상태를 신뢰할 수 있게 된다. VO vs. Entity값 객체와 엔티티(Entity)를 비교할 때 가장 큰 차이점은 식별자의 유무이다. • Entity는 식별자가 있어야 하며, 식별자가 동일하면 동일한 객체로 취급된다.• 반면 VO는 식별자가 없고, 내부의 모든 값이 동일해야만 동등한 객체로 취급된다.// Entity 예시 public class User { private Long id; private String name; // getters, setters, equals, hashCode 등 } // Value Object 예시 public final class Address { private final String street; private final String city; public Address(String street, String city) { this.street = street; this.city = city; } // getters, equals, hashCode 등 }보통 두개의 개념을 혼동해서 말하는 사람들을 자주 봤는데, 구분하는 기준은 생각보다 명확하다.VO은 값만 같으면 같은 객체로 인식한다는 점에서, 정말 데이터 그 자체를 담는 용도로 생각하면 쉽다.생성 후 그 속성이 변하지 않기에 VO는 불변성의 특징을 가진다.엔티티는 DTO로서, 계층 간 데이터를 전송하는데 활용될 수 있다.해당 내용에 있어서는 기술블로그에 정리한 적이 있다. -> [데이터베이스] DTO, DAO, VO의 개념 (+ DTO와 VO의 차이) 일급 시민: 일급 컬렉션을 활용하자 일급 시민이란 다른 요소들이 가능한 모든 연산을 지원하는 개념이다. 예를 들어, 함수형 프로그래밍 언어에서는 함수가 일급 시민으로 취급되는데, 이는 함수를 변수에 할당하거나 파라미터로 전달할 수 있고, 함수의 결과로 반환될 수도 있음을 의미한다.아직 개념이 잘 안잡힌 것 같아서, 더 공부해야할 내용인 것 같다. 일급 컬렉션: 컬렉션을 일급으로 다루기일급 컬렉션은 컬렉션만을 유일한 필드로 가지는 객체이다. 이는 컬렉션을 다른 객체처럼 의미 있게 다룰 수 있게 해주고, 컬렉션을 가공하는 로직을 캡슐화하여 보다 깨끗한 코드와 더 나은 테스트 가능성을 제공한다.public class OrderList { private final List<Order> orders; public OrderList(List<Order> orders) { this.orders = new ArrayList<>(orders); } public List<Order> getOrders() { return new ArrayList<>(orders); } public void addOrder(Order order) { orders.add(order); } } Enum의 특성과 활용Enum은 상수의 집합이며, 상수와 관련된 로직을 담을 수 있는 공간이다. 상태와 행위를 한 곳에서 관리할 수 있어 코드의 가독성과 유지보수성을 크게 향상시킬 수 있다. 도메인 개념에 대한 종류와 기능을 명시적으로 표현할 수 있으며, 만약 변경이 잦은 개념이라면 Enum보다는 DB로 관리하는 것이 나을 수 있다. 항상 거의 변하지않는 데이터들이 우리 서비스 내에 있을 때, Enum으로 관리할지 DB로 관리할지 고민하게 되는 것 같다. 그 양이 너무 많을 때에는, 프로젝트 메모리에 올려두는 것이 좋지 않을 것 같아 DB에 넣을 때도 있고, 불빌요한 쿼리문을 쏘지 않고자 Enum으로 관리할 때도 있기 때문이다. 더 많은 경험을 쌓아서 기준을 만들어야 될 것 같다.다형성: if문을 제거하고 OCP 지키기반복되는 조건문을 다형성으로 제거하여 OCP(Open/Closed Principle)를 지킬 수 있다. OCP는 “확장은 열려 있고, 수정은 닫혀 있다”는 원칙으로, 새로운 기능 추가 시 기존 코드를 변경하지 않고 확장할 수 있게 한다. 이를 위해 조건에 따라 다른 동작을 수행해야 할 때, 다형성을 활용해 조건과 행위를 분리할 수 있다. 숨겨져 있는 도메인 개념 도출하기지뢰게임 시스템로 리펙토링 중에, 서비스 기획과 의도를 재파악 한 뒤, 설계를 변경하게 되는 순간들이 많았었다.이처럼 도메인 지식은 만드는 것이 아니라, 발견하는 것이다. 시스템 초기 설계에서는 이 발견을 미리 최대한 예측하여 확장 가능하도록 구현하고, 후에 확장 단계에서 재정립해가는 것이다.그러므로 개발 초기에 확장 가능한, 객체 지향의 핵심Loose Coupling(느슨한 결합)과 High Cohesion(높은 응집)에 기반하여 개발해야할 것 같다.여기까지 백엔드 클린 코드, 테스트 코드강의의 1주차 내용 정리글을 작성해보았다.초반에는 그래도 수업이 좀 따라갈만 했는데, 슬슬 어려운 내용들이 많이 나오는 것 같아 시간을 좀 더 써야할 것 같다는 생각이 들었다.그리고 지금은 헷갈리는 개념이 나올 때마다 예제 코드를 만들어보거나 참고하는데, 이 방법이 가장 빠르게 이해할 수 있는 방법인 것 같다. 아쉬웠던 점은, 마지막 섹션은 수업 내용을 너무 따라가려고만 했는데, 끝나고 나니 남는게 좀 적었다. 이해 못한 부분이 있으면 복습한 뒤, 다음 차트로 넘어가도 좋을 것 같다.

인프런워밍업클럽클린코드

윤승현

[인프런 워밍업 클럽 CS 2기] - CS 1주차 미션

운영체제 1.while(true){ wait(1); // 1초 멈춤 bool isActivated = checkSkillActivated(); // 체크 }위 코드는 1초 마다 플레이어가 스킬을 사용했는지 체크하는 코드입니다.이 방식은 폴링방식입니다.1초마다 체크하기 때문에 성능에 좋지 않습니다.이를 해결하기 위한 방식으로 어떤 걸 이용해야 할까요? 인터럽트 방식을 사용한다. 인터럽트는 폴링 방식의 단점을 해결한 방식인데 CPU가 입출력 관리자에게 입출력 명령을 내리고 자기는 다른 작업을 계속하는 방식이다. 프로그램과 프로세스가 어떻게 다른가요?  프로그램은 컴퓨터 관점에서 하드 디스크. 즉, 저장 장치(HDD, SDD)만 사용하는 수동적인 존재이다.반면 프로세스는 메모리도 사용하고 운영체제의 CPU 스케줄링 알고리즘에 따라서 CPU도 사용하고 필요에 따라 입력과 출력을 하기 때문에 능동적인 존재이다. 멀티프로그래밍과 멀티프로세싱이 어떻게 다른가요?멀티 프로그래밍은 메모리에 여러 개의 프로세스가 올라온 것이다.멀티 프로세싱은 CPU가 여러 개의 프로세스를 처리하는 것이다. 운영체제는 프로세스를 관리하기 위해서 어떤 것을 사용하나요?PCB(Process Control Block)를 사용한다.포인터, 프로세스 상태, 프로세스 ID, 프로그램 카운터, 레지스터 정보, 메모리 관련 정보, CPU 스케줄링 정보 등 여러 정보가 있다. 컨텍스트 스위칭이란 뭔가요?컨텍스트 스위칭은 프로세스를 실행하는 중에 다른 프로세스를 실행하기 위해 실행 중인 프로세스의 상태를 저장하고 다른 프로세스의 상태 값으로 교체하는 작업이다.  자료구조와 알고리즘 여러분은 교실의 학생 정보를 저장하고 열람할 수 있는 관리 프로그램을 개발하려고 합니다.이 때 여러분이라면 학생의 정보를 저장하기 위한 자료구조를 어떤 걸 선택하실 건가요?이유를 함께 적어주세요. 해시 테이블을 사용할 것 같다.해시 테이블은 평균적으로 O(1) 시간 복잡도를 가지기 때문에 학생의 ID나 이름과 같은 고유한 값을 해시 키로 사용하면 학생 정보를 빠르게 검색할 수 있기 때문이다.해시 테이블은 평균적으로 데이터의 삽입과 삭제도 매우 빠르게 처리된다. 새로운 학생을 추가하거나 어떤 학생을 제거할 때도 효율적이라고 생각한다. 여러분은 고객의 주문을 받는 프로그램을 개발하려고 합니다.주문은 들어온 순서대로 처리됩니다.이 때 여러분이라면 어떤 자료구조를 선택하실 건가요?이유를 함께 적어주세요. 주문이 들어온 순서대로 처리된다는 점을 고려할 때 "큐(Queue)" 자료구조를 선택할 것 같다.큐는 FIFO 방식으로 작동하는 자료구조다. 그래서 먼저 들어온 주문이 먼저 처리되기 때문에 주문 처리 시스템에 적합한 구조라고 생각한다.  

CS미션

윤영선

[인프런 워밍업 스터디 클럽 2기 FE] 1주차 발자국

1주차 발자국강의 수강이번 주에 객체지향 프로그래밍(OOP)까지 강의를 수강했습니다.취업준비와 진행중인 프로젝트 일정과 겹처서 이번 주에는 객체지향 프로그래밍(OOP)까지 학습하였습니다. 매일 1~2시간씩이라도 강의를 듣고 실습을 진행하고자 했고 공부 방식은 강의를 들으며 실습을 통해 배운 내용을 코드에 적용해보는 방식을 선택했습니다. 자바스크립트 개념을 점검한다는 마음으로 강의를 수강했고 함수와 객체지향 개념을 집중적으로 다루었으며 실습을 통해 이론을 체계적으로 익힐 수 있었던 것 같습니다. 학습 요약시간이 부족하여 계획한 모든 학습을 다 진행하지는 못했지만 강의에서 배운 내용을 하나하나 실습하면서 놓쳤던 부분이나 몰랐던 부분을 다시 복습할 수 있었습니다. 자바스크립트의 기초 개념과 함수와 객체지향 개념을 실습하면서 코드의 구조를 더 잘 이해하게 되었고 실제 코드 작성에 적용하는 연습을 했습니다. 실습 과정에서 코드를 수정하고 예상한 결과를 얻기까지 반복적인 실험을 통해 점차 이해도를 높였습니다. 단순 강의내용을 따라하는 것이 아닌 다른 방식으로도 작성해보려고 노력했습니다. 강의 내용 요약자바스크립트 기초var, let, const의 차이점, 호이스팅, 스코프, 자바스크립트의 데이터 타입과 타입 변환에 대해 학습했습니다.var의 함수 레벨 스코프와 let, const의 블록 레벨 스코프 차이점에 대해 더 깊이 이해할 수 있었습니다.  느낀 점이전까지 자바스크립트의 변수 선언 방식에 대해 큰 생각 없이 의미없이 사용하고 있었는데 이번 기회를 통해 변수의 선언을 이해하고 적합한 방식으로 사용하는 것을 체계적으로 정리하고 연습할 수 있어서 좋았던 것 같습니다. var와 let의 차이와 호이스팅이 실제 코드에서 어떻게 적용되는지를 정리했고 명확한 변수 사용을 위해 연습한 시간이었습니다. 프로젝트를 진행했을 때 var를 잘못 사용하여 문제가 생긴 적이 있었는데 호이스팅에 대해서 배우면서 다시 생각해볼 기회가 생겼고 기존에 배운 개념을 다시 한번 점검하고 상기시킬 수 있었습니다. 윈도우 객체 및 DOMDOM이란 웹 페이지의 구조를 트리 형태로 표현하는 방식이를 통해 자바스크립트로 HTML 요소를 선택하고 조작 가능!getElementById, querySelector 등의 메서드를 사용하여 HTML 요소를 선택하고createElement, removeChild 등의 메서드로 DOM을 조작하는 방법을 학습    느낀 점동적 데이터를 사용하거나 DOM 조작에 자신이 없어서 항상 어렵게 느끼고 부담스러워하며 사용을 했었는데 예제와 실습을 반복하면서 자신감을 되찾아갔던 것 같습니다. DOM 트리 구조를 다시 한번 복습하고 이를 예제와 함께 실습하니 DOM에 익숙해졌고 비효율적으로 작성했던 코드들을 배운 내용을 바탕으로 수정해보았더니 더 개선됨을 확인 할 수 있었던 것 같습니다. querySelectorAll을 사용해본 적이 없었는데 이번 기회로 공부하고 사용해봤더니 다수의 요소를 선택하고 처리하는 방식이 매우 유용하다는 것을 배웠으며, 앞으로 더 효율적인 코드를 작성하는 데 많은 도움이 될 것이라고 느꼈습니다. Event이벤트 처리 방식, addEventListener를 사용해 입력, 클릭 등 다양한 사용자 액션을 감지하고 처리하는 방법 학습이벤트 버블링(Event Bubbling)과 이벤트 캡처링(Event Capturing), 이벤트 위임(Event Delegation) 학습  느낀 점아직도 간단한 작업이거나 동적인 요소가 없는 경우 메인 페이지나 헤더, 푸터를 작성할 때는 여전히 onclick 속성을 사용해 이벤트를 처리하는 방식이 익숙하고 편리하게 느껴져 자주 활용하곤 합니다. 그러나 이번 강의를 통해 성능 측면에서 더 나은 방법인 이벤트 위임 방식에 대해 다시 한 번 깨달을 수 있었습니다. 리스트나 테이블 같은 동적 UI에서 부모 요소에 이벤트를 위임하면 더 깔끔하고 성능이 향상된 코드를 작성 가능한 것을 알고 있었으나 간편함으로 인해 필요할 때만 사용했던 것을 반성하게 되었습니다. onclick의 간편함에 의존하기보다는 이벤트 위임 방식을 더 많이 연습하고 익숙해져야겠다고 느꼈습니다. 이번 학습을 통해 성능을 고려한 코드 작성 방법을 다시 한 번 상기할 수 있는 좋은 기회였습니다. 자바스크립트 중급삼항 연산자, 구조 분해 할당, 전개 연산자map, filter, reduce 등의 배열 메서드와 this 키워드의 사용법을 학습비동기 처리 방식과 Event Loop의 작동 원리 학습 느낀 점제가 생각했을 때 자바스크립트에서 가장 기본이고 자주 사용되는 this와 배열 메서드들에 대해 학습할 수 있어서 좋았습니다. 데이터를 처리하거나 배열을 변환할 때 입력값을 배열로 보내거나 응답값을 배열로 받아야 하는 경우가 많았습니다.map과 filter 같은 배열 메서드는 코드의 가독성을 높일 뿐만 아니라 성능 면에서도 필수적인 도구라고 생각했고 그만큼 자주 사용되기때문에 혹시 잘못 알고 있었던 부분이 있는지 점검해가면서 강의를 수강했고 올바르게 사용하고 있는지 확인할 수 있어서 좋았습니다. this 키워드는 항상 신경을 많이 쓰는 부분 중 하나였습니다. 의도한 객체가 아닌 전역 객체를 참조해서 예상치 못한 결과를 초래하는 경우가 종종 있었고 그때마다 디버깅에 많은 시간을 할애한 경험이 많았습니다. 이번 학습을 통해 다시 한 번 this 키워드의 사용법을 점검하고 의도한 대로 사용하고 있는지 꼼꼼히 확인하면서 진행했습니다. 앞으로는 이런 문제를 더 쉽게 예방할 수 있을 것 같아 자신감이 생겼습니다.비동기 처리 방식과 Event Loop의 작동 원리를 학습하면서 자바스크립트가 싱글 스레드 언어임에도 비동기 처리를 어떻게 구현하는지 명확히 이해할 수 있었습니다. Event Loop의 작동 원리를 배우면서 코드가 동기적으로 실행되는 것처럼 보이지만 실제로는 비동기적으로 처리되는 부분을 더 잘 관리할 수 있는 방법을 알게 된 점이 특히 도움이 되었습니다. 이 덕분에 앞으로 비동기 작업을 보다 효율적으로 처리할 수 있을 것 같습니다. OOP(객체 지향 프로그래밍)OOP의 개념과 자바스크립트의 프로토타입 기반 상속 구조를 학습ES6 클래스와 super() , 상속을 통해 객체 지향 설계의 기본 원칙을 자바스크립트에서 구현하는 방법 학습  느낀 점객체지향 프로그래밍은 클래스 기반으로 코드를 작성하면서 유지보수성과 확장성을 크게 개선할 수 있는 코드로서 반드시 알아야하는 개념이라 생각합니다. 생성자 함수를 사용해 객체를 생성하는 방법은 이제 지양하고 자바스크립트를 배운 이상 ES6 클래스를 사용하여 직관적인 코드를 작성해야한다고 생각합니다. 프로젝트에서 확장 가능하고 관리하기 쉬운 코드를 작성하기 위해 반드시 필요한 개념이라 생각해서 아는 개념이었지만 집중해서 들었던 것 같습니다. 상속의 경우 코드 중복을 줄이고 재사용성을 높이는 데 매우 유용하지만 잘못 사용하면 오히려 복잡성을 초래할 수 있다는 점을 유의하며 학습에 집중했고 실습을 통해 이러한 부분을 신경 쓰며 적용해 보았습니다. 1주차 회고이번 주는 취업 준비로 인해 충분한 시간을 투자하지 못해 계획했던 학습 목표를 완벽히 달성하지는 못했습니다. 그러나 매일 조금씩이라도 시간을 내어 학습을 이어갔고 배운 내용을 즉시 실습하면서 개념을 내 것으로 만들기 위해 노력했습니다. 단순히 강의만 듣는 것이 아니라 직접 코드를 작성하며 개념을 이해하고 복습하는 과정이 매우 효과적이었습니다. 이과정에서 기존에 알고 있던 자바스크립트 지식을 다시 점검할 수 있었고 오랜만에 다루면서 잊고 있던 개념들도 복습할 수 있었습니다. 그동안 자주 사용하지 않았던 개념들을 이번 기회에 다시 학습하고 자주 사용하던 개념은 맞는지 점검해가면서 지식을 보충하고 이해의 폭을 넓힐 수 있어 매우 유익한 시간이었습니다. 개선할 점과 2주차 계획1주차에 미처 완료하지 못한 부분을 평일 시간 관리를 철저히 하여 강의 커리큘럼을 모두 따라갈 계획입니다. 주말과 휴일을 활용해 부족한 부분을 보충할 예정입니다. 2주차에는 아직 수강하지 못한 강의를 마무리하고 과제를 저만의 방식으로 변형하면서 주요 기능을 유지하는 방향으로 진행할 것입니다. 목표는 미완성 과제를 완료하고 최소한 진도를 따라잡아 중간 점검 전까지 과제 3개 이상을 완벽하게 마무리하는 것입니다. 배운 내용을 실습에 적용해 개념을 확실히 다지고 워밍업 클럽을 완주를 목표로 달려나가겠습니다! 

프론트엔드워밍업클럽스터디프론트엔드

워밍업 클럽 2기 백엔드 클린코드&테스트 코드 1주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법 섹션 1~5까지의 강의 수강과 미션을 진행하고 남긴 1주차 발자국입니다. 강의Section 1: 소개환경 설정 및 용어 정리Section 2: 추상화추상과 구체'하나의 세계 안에서는 추상화 레벨이 동등해야 한다'는 메시지가 인상 깊었습니다. 이는 미래의 나와 동료 개발자들을 위한 배려라는 점이 특히 의미있었습니다. 개발 당시에는 해당 도메인과 비즈니스 로직이 머릿속에 선명하기 때문에 구체적 표현이 크게 문제되지 않습니다. 하지만 시간이 지나 다시 코드를 읽을 때 이해가 어려워 시간을 낭비한 경험이 많았습니다. 이러한 문제는 추상화를 통해 해결할 수 있으며, 이것이 바로 미래의 나를 배려하는 방법이라고 깨달았습니다.네이밍메서드명 작성 시 메서드 이름과 매개변수를 전치사로 연결하는 방법은 새로운 인사이트였습니다. 특히 오버로딩을 적용할 때 효과적일 것이라는 생각이 들었습니다.Section 3: 논리와 사고의 흐름부정어 처리부정어 처리를 위해 별도의 메서드를 만드는 방식은 깊이 고민해보지 않았던 부분입니다. 강의에서 제시된 코드를 보니 직관성이 크게 향상되어, 필요한 접근이라고 느꼈습니다.Optional그동안 Optional을 깊이 이해하지 못한 채 관성적으로 사용해왔습니다. 강의에서 Optional을 매개변수로 사용하지 말아야 하는 이유와 orElse()와 orElseGet()의 소스코드를 분석하면서, Optional의 잘못된 사용을 반성하게 되었습니다.Section 4: 객체 지향 패러다임SOLID이 부분은 미션과 밀접하게 연관되어 있었습니다. 개발 공부 과정에서 SOLID 원칙을 자주 접했지만, 실제 코드에 적용하기는 어려웠습니다. 따라서 SOLID에 대한 개인적 견해를 질문 형태로 정리하여, 작성한 코드가 이 원칙들을 잘 반영하고 있는지 확인하는 체크리스트로 활용하면 좋겠다고 생각했습니다.Section 5: 객체 지향 적용객체 지향 구현을 위한 다양한 도구VO, 일급 컬렉션, Enum, 다형성 등 객체 지향을 실현하기 위한 유용한 도구들이 소개되었습니다. VO와 일급 컬렉션은 간단한 미션에서만 적용해보았고, 실제 프로젝트에서는 활용하지 못했습니다. 이는 필요성을 제대로 인식하지 못했기 때문입니다. 강의를 통해 이러한 도구들의 용도와 가치를 깊이 이해하게 되었습니다. 시간 관계 상 프로젝트를 개선하지는 못했지만, 객체 지향을 효과적으로 적용할 수 있는 방향성을 발견할 수 있었습니다.1주차 강의 회고Keep수강과 미션을 일정에 맞춰서 진행함강의를 들으면서 이전에 진행한 프로젝트에 적용할 부분을 찾아본 것Problem시간 분배강의 수강에 예상보다 많은 시간이 소요됨 자소서, 새로 시작하는 프로젝트에 쓸 수 있는 시간이 부족했음Try시간 분배 다시 생각일주일 계획 짜기 DAY4 미션아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.  요구사항 ✔ 사용자가 생성한 '주문'이 유효한지를 검증하는 메서드.✔ Order는 주문 객체이고, 필요하다면 Order에 추가적인 메서드를 만들어도 된다. (Order 내부의 구현을 구체적으로 할 필요는 없다.)✔ 필요하다면 메서드를 추출할 수 있다.public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; } 리펙토링 후 코드public class OrderService { private final LogPrinter logPrinter; public OrderService() { logPrinter = new LogPrinter(this.getClass()); } public boolean validateOrder(Order order) { if (order.hasNoItems()) { logPrinter.printInfo("주문 항목이 없습니다."); return false; } if (order.hasInvalidTotalPrice()) { logPrinter.printInfo("올바르지 않은 총 가격입니다."); return false; } if (order.hasNoCustomerInfo()) { logPrinter.printInfo("사용자 정보가 없습니다."); return false; } return true; } } public class Order { private final List<Item> items; private final Customer customer; public Order(List<Item> items, Customer customer) { this.items = items; this.customer = customer; } public boolean hasNoItems() { return items == null || items.isEmpty(); } public boolean hasInvalidTotalPrice() { return calculateTotalPrice() <= 0; } public long calculateTotalPrice() { return items.stream() .mapToLong(Item::getPrice) .sum(); } public boolean hasNoCustomerInfo() { return customer == null || customer.isNotExist(); } } 구현 과정if문 depth 줄이기if문 조건절 메서드로 바꾸기(추상)로그 출력을 메서드로 빼서 일괄적으로 처리하게 하기구현 중 고민 사항Order 객체가 items의 총 가격을 계산하는 기능의 책임을 가지는 것이 맞는가?계산기 객체 추가 구현 고려validateOrder(Order order) 기능 자체가 Order 객체가 가져야 할 책임이지 않는가?validateOrder(Order order) 메서드를Order 객체로 옮기는 것 고려하기구현 후 아쉬웠던 점매직 넘버, 스트링 처리하지 않은 것로그 출력 기능에서 warning등 다른 메서드 활용을 고려하지 않은 것Order 객체에서 items나 customer 필드 관련 null 검증을 생성자에서 하지 않은 것 2. SOLID에 대하여 자기만의 언어로 정리해 봅시다.SRP한 클래스는 하나의 책임만 가져야 한다클래스를 변경하는 이유는 오직 하나여야 한다‘한 클래스, 기능이 가지는 책임의 범위가 어디까지 인가?’ 생각하기OCP확장에는 열려 있고, 변경에는 닫혀 있어야 한다기존 코드의 변경을 최소화 하면서 새로운 기능을 추가할 수 있어야 한다객체 지향에 집착하지 않기. 요구 사항에 따라 객체 지향을 고수하는 것보다 절차 지향이 확장에 더 열려있을 수 있다.LSP상위 타입의 객체를 하위 타입의 객체로 치환해도 프로그램은 정상적으로 동작해야 한다하위 클래스는 상위 클래스의 규약을 지켜야 한다'어떤 동작을 추상화하면 모든 하위 클래스가 올바르게 구현할 수 있을까?' 생각하기ISP클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다큰 인터페이스를 작은 단위로 분리해야 한다‘필요로 하는 메서드만 제공하는가?’ 생각하기DIP고수준 모듈은 저수준 모듈에 의존하지 않아야 한다. 둘 모두 추상화에 의존해야 한다추상화는 세부 사항에 의존하지 않야야 한다. 세부 사항이 추상화에 의존해야 한다SOLID원칙을 위한 원칙이 되지 않도록 하자팀의 상황과 프로젝트의 요구 사항에 맞게 사용하자미션 회고리펙토링 미션을 시간에 쫒겨 급하게 한 것이 아쉽다. 회고록을 작성하는 중에도 계속 아쉬운 점이 보임.일정 때문에 다른 사람의 미션을 읽어보지 못한 것이 아쉽다.같은 이유로 코드 리뷰를 받지 못한 것이 아쉽다.SOLID에 대한 내 생각을 질문 형태로 남기는 것은 좋은 생각이었던 것 같다. 이를 발전시켜 더 구체적인 질문으로 남기기  

[워밍업 클럽_CS 전공 스터디 2기] 1주차 미션

운영체제1번 문제while(true){ wait(1); // 1초 멈춤 bool isActivated = checkSkillActivated(); // 체크 }위 코드는 1초 마다 플레이어가 스킬을 사용했는지 체크하는 코드입니다. 이 방식은 폴링방식입니다. 1초마다 체크하기 때문에 성능에 좋지 않습니다. 이를 해결하기 위한 방식으로 어떤 걸 이용해야 할까요? 답변:폴링은 조사라는 의미를 갖고 있으며, cpu가 입출력관리자에게 계속 확인을 하는 구조이다. 이것은 CPU낭비이므로 해결책으론 인터럽트가 있다.입출력관리자가 인터럽트 신호를 보낼때까지 cpu는 다른일을 하고. 작업을 완료한 입출력관리자는 인터럽트를 보낸다. 그럼 CPU는 인터럽트 서비스 루틴을 실행시켜 작업을 완료한다. 2번 문제프로그램과 프로세스가 어떻게 다른가요? 답변:프로그램은 저장장치에 저장된 명령문의 집합체이다. .exe실행파일 같은것들이 있다.프로세스는 이 프로그램이 메모리 위에 올라가서 실행중인 상태다. 이상태에선 cpu도 쓰고 입출력 장치도 쓰고 그런다. 3번 문제멀티프로그래밍과 멀티프로세싱이 어떻게 다른가요? 답변:메모리위에 여러개의 프로세스가 올라온것을 멀티프로그래밍이라 부른다.CPU가 시분할 방식으로 여러개의 프로세스를 처리한것을 멀티프로세싱이라 부른다.올라온것과 처리한것의 차이가 아닌가 싶다 4번 문제운영체제는 프로세스를 관리하기 위해서 어떤 것을 사용하나요?답변:PCB(process control block)을 만들어서 사용한다.1)프로세스관리를 위해 운영체제는 해당 프로세스 정보가 담긴 PCB를 만든다.2)PCB들은 연결리스트라는 자료구조로 저장되어서 해당 프로세스가 종료될때 연결리스트에서 그 PCB도 제거한다(*연결리스트는 각각의 데이터가 다음 데이터를 연결하는 구조로 되어있는 자료구조다.)(*PCB의 구조는 포인터,프로세스 상태,프로세스 ID,프로세스 카운터,레지스터 정보,메모리 관련 정보,CPU 스케줄링 정보 등등으로 이루어져있다.) 5번 문제컨텍스트 스위칭이란 뭔가요?답변:프로세스 상태를 교체하는 작업입니다.예를 들자면 CPU점유시간이 다됐거나,인터럽트가 발생했거나,I/O요청이 있을때 컨텍스트 스위칭이 발생됩니다컨텍스트 스위칭 하는 과정을 설명하자면1)실행중인 프로세스 A가 교체되야할때. 현재 CPU의 레지스터 값을 프로세스A의 PCB A에 저장합니다2)그다음 실행될 프로세스 B의 PCB B에서 레지스터값을 CPU 레지스터로 가져오면. 프로세스 B가 실행됩니다.PCB에 변경하는 값들로는 프로세스 상태,프로그램 카운터,레지스터 정보,메모리 관련 정보 등이 있습니다. 자료구조와 알고리즘1번 문제여러분은 교실의 학생 정보를 저장하고 열람할 수 있는 관리 프로그램을 개발하려고 합니다. 이때 여러분이라면 학생의 정보를 저장하기 위한 자료구조를 어떤 걸 선택하실 건가요? 이유를 함께 적어주세요.답변:배열을 사용하겠음. 학생수에 변동이 크지않기 때문임 2번 문제 여러분은 고객의 주문을 받는 프로그램을 개발하려고 합니다. 주문은 들어온 순서대로 처리됩니다. 이 때 여러분이라면 어떤 자료구조를 선택하실 건가요?답변:큐의 피포방식(선입선출)을 선택하겠음.

korong lee

[워밍업 클럽 스터디 백엔드 2기] 1주차 회고

출처 : Readable Code: 읽기 좋은 코드를 작성하는 사고법 - 박우빈 1. 학습 내용 요약추상(섹션 1, 2)클린 코드를 추구하는 이유바로 “가독성” → 유지보수가 쉬움 = 비용 감소 이름짓기이름을 짓는다 = 추상화단수와 복수를 구분하기이름 줄이지 않기은어/방언 사용 X좋은 코드를 보고 습득하기 메서드 선언부메서드명추상화된 구체를 유추 가능한 적절한 의미의 이름파라미터와 연결 지어 풍부한 의미 전달파라미터파라미터 타입, 개수, 순서를 통해 의미 전달외부와 소통하는 창반환타입메서드 시그니처에 납득이 가능한 적절한 타입의 반환값void 대신 충분히 반환할 만한 값을 고민 논리, 사고의 흐름(섹션 3)Early return그냥 if에서 바로 리턴하고, 기존 else를 if로 재작성하면 이전 if문의 조건 정보는 기억할 필요 X → Early return의 장점따라서 Early return으로 else의 사용을 지양할 것! switch문도 마찬가지! 사고의 depth 줄이기중첩 분기문, 중첩 반복문인지해야 하는 조건들이 너무 많음메서드로 추출해서 다 분리하면 bestc. BUT. 무조건 1 depth로 만들어라가 아님!!! → 사고 과정의 depth를 줄이는 것이 핵심사용할 변수는 가깝게 선언하기 부정어구 사용 자제부정어구를 쓰지 않아도 되는 상황인지 체크 → right, left, up, down부정의 의미를 담은 다른 단어가 존재하는지 고민 or 부정어구로 메서드명 구성 해피 케이스와 예외 처리예외 처리 방법예외가 발생할 가능성 낮추기어떤 값의 검증이 필요한 부분은 주로 외부 세계와의 접점 → 사용자 입력, 객체 생성자, 외부 서버 요청 등의도한 예외와 예상하지 못한 예외 구분하기 SOLID(섹션 4)SRP: Single Responsibility Principle하나의 클래스는 오로지 하나의 책임 만을 갖는다.객체의 공개 메서드, 필드, 상수 등은 해당 객체의 책임에 의해서만 변경 가능2. OCP: Open-Closed Principle확장에는 열려 있고, 수정에는 닫혀 있다.시스템의 기능 확장3. LSP: Liskov Substitution Principle부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환 가능해야 한다.불필요한 타입 체크 문제4. ISP: Interface Segregation Principle클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않는다.인터페이스를 잘게 나눔5. DIP: Dependency Inversion Principle상위 수준의 모듈은 하위 수준의 묘듈에 의존하지 않는다.모두 추상화에 의존할 것 Value Object(섹션 5)도메인의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해, 불변성, 동등성, 유효성 검증 등을 보장불변성 : final 필드, setter 금지동등성 : 서로 다른 인스턴스여도 내부 값이 같으면 같은 객체 → equals()와 hashCode() 재정의 필요유효성 검증 : 객체가 생성되는 시점에 값에 대한 유효성 보장해당 객체를 마치 데이터 타입처럼 사용 가능 VO vs EntityEntity는 식별자 가 존재 → 식별자가 같으면 동일한 객체VO는 식별자 없이 내부의 모든 값이 같아야 동등한 객체 → 전체 필드가 다같이 식별자 역할 일급 컬렉션다른 요소에게 사용 가능한 모든 연산을 지원하는 요소 → 변수로 할당 or 파라미터로 전달 or 함수의 결과로 반환컬렉션을 포장해 컬렉션만을 유일하게 필드로 가지는 객체컬렉션을 추상화하며 의미를 담을 수 있고, 가공 로직의 보금자리가 생김만약 getter로 컬렉션을 반환할 일이 생긴다면, 외부 조작을 피하기 의해 꼭 새로운 컬렉션으로 만들어서 반환  Enum상수의 집합으로, 상수와 관련된 로직을 담을 수 있는 공간경이 잦을 경우 Enum 보다 DB 관리를 추천 2. KPTKeep(칭찬하고 싶은 점)코드를 직접 따라 치면서 이론을 실무에 적용해 이해하도록 노력했다.미션을 밀리지 않고 시간에 맞춰 제출했다.학습한 내용을 노션에 정리하고 있다. Problem(아쉬웠던 점)코드를 분석하며 학습을 진행하니 학습 속도가 다소 늦어지고, 오래 걸린다.후반부에 들어서 강의 내용도 어려워지면서 이해가 안 되어도 그냥 넘어가는 경우도 간혹 발생한다.리팩토링 기록을 깃헙에도 남기고 싶었는데 시간 관계 상 하지 못했다. Try(보완하고 싶은 점)학습에 우선순위를 둘 것(강의듣기 -> 리팩토링 -> 노션정리)이해가 안 되는 부분은 반복+질문으로 해결할 것이론 정리와 리팩토링 학습을 골고루 진행할 것학습 시간 제한을 두어 타이트하게 일정을 관리할 것  3. 미션 회고미션 1 : 드립 커피 추출을 추상과 구체화로 표현했다. 구체화의 단계를 어느 단계까지 low하게 내려가야 할 지 다소 난감했던 기억이 난다. 추후 다른 사람들의 미션 내용도 확인하면서 조금 더 학습할 예정이다.미션 2 : 코드 리팩토링 과제였다. 학습한 내용을 단계별로 적용하는 과정을 남기고 싶었는데 이게 단계를 명확하게 구분하기 어렵고, 순서도 머릿속에 뒤죽박죽이 되어 step 별로 정리하는데 애를 먹었다. 실제로 공부한 내용을 적용해 볼 수 있는 시간이라 재밌었다.  4. 느낀점미션2를 제출하면서 다른 분들이 제출한 내용도 함께 봤는데 역시 잘 하시는 분들이 어마어마하게 많았다. 강의내용에도 허덕이는 스스로에게 자괴감이...😢 그래서 이번주 결론은 다음주는 더 빡세게 공부하자!!!!!!!!!

백엔드백엔드워밍업스터디클럽클린코드발자국

세뇨르

인프런 워밍업 클럽2기 백엔드 - 발자국 1주차 (회고)

인프런 워밍업 클럽 2기 발자국 1주차1. 한 주를 돌아보며..인프런 워밍업 클럽, 읽기 좋은 코드를 작성하는 사고법 스터디도 벌써 한 주가 지났다.사실 시간에 비해 한 주 동안 공부해야 할 양이 많은 것은 아니었지만 이해하고 몸으로 체화하는 데 시간을 더 많이 쓴 것 같다.이번 주는 추상 / 논리,사고의 흐름 / 객체 지향 패러다임에 대해 공부했다. Section 1: 추상 (抽象)이번 주에 '추상'에 대해 배웠다. 추상과 구체의 개념, 적절한 이름 짓기의 중요성, 메서드 선언부의 의미, 추상화 레벨 맞추기, 그리고 매직 넘버와 매직 스트링의 사용에 대해 학습했다.추상화 레벨을 맞추는 게 생각보다 어려웠다. 너무 추상적이면 이해하기 어렵고, 너무 구체적이면 코드가 복잡해지는데, 이 balance를 맞추는 게 관건인 것 같다. 매직 넘버를 상수로 바꾸는 건 알고 있었지만, 이게 가독성과 유지보수성에 큰 영향을 미친다는 점을 새삼 깨달았다.Section 2: 논리, 사고의 흐름인지적 경제성, Early return, 사고의 depth 줄이기, 공백 라인과 부정어 사용, 해피 케이스와 예외 처리, 그리고 stream API와 Optional 사용에 대해 배웠다.Early return이 코드의 가독성을 크게 높인다는 점이 인상 깊었다. 지금까지 작성한 코드를 되돌아보니 불필요하게 복잡한 구조가 많았던 것 같다. 또한, 부정어 사용을 줄이는 것이 생각보다 어려웠는데, 이를 위해 메서드 추출 등의 방법을 사용할 수 있다는 점이 새로웠다.Section 3: 객체 지향 패러다임객체 간 협력과 책임, 관심사의 분리, getter/setter 사용 자제, 객체에 메시지 보내기, SOLID 원칙, 그리고 DI/IoC에 대해 학습했다.getter/setter를 무분별하게 사용하는 것이 객체 지향적이지 않다는 점을 배웠다. 객체에 메시지를 보내는 방식으로 코드를 작성하는 것이 더 객체 지향적이라는 점이 인상 깊었다. SOLID 원칙은 들어본 적은 있었지만, 실제로 적용하는 것은 쉽지 않을 것 같다. 특히 단일 책임 원칙(SRP)을 지키는 것이 가장 어려울 것 같다. 2. 미션이번 주에는 추상과 구체에 대해 생각해보고 생각나는 추상과 구체의 예시를 적어보는 미션이었다. (Day2)나는 러닝으로 추상과 구체를 나누어 보았다.러닝을 한다.- 오른발 뒤꿈치가 지면에 닿으며, 발바닥이 지면을 따라 앞으로 구르듯 펴진다.- 오른발이 지면을 밀어내는 동안, 왼발은 지면에서 떨어지면서 무릎이 구부러지고 몸 앞쪽으로 들어 올려진다.- 왼발이 앞으로 나아가는 동시에 오른팔이 앞으로 움직이고, 왼팔은 뒤로 젖혀진다.- 몸통이 약간 앞으로 기울어진 채 오른발을 축으로 전방으로 회전하듯 움직인다.- 왼발 뒤꿈치가 지면에 닿으면서 1-4번 과정이 반대 방향으로 반복된다.'러닝'이라는 추상적인 개념을 구체적인 동작으로 풀어 설명했다. 처음에는 '달린다'라고만 생각했는데, 실제로 각 동작을 하나하나 떠올려보니 생각보다 복잡한 과정이었다. 이런 식으로 추상적인 개념을 구체화하는 연습을 하면 코드 작성 시에도 도움이 될 것 같다. 또 다른 미션은 코드를 리팩토링하는 것과 SOLID 원칙을 정리하는 미션이었다. (Day4)https://www.inflearn.com/blogs/8385[미션 Day4] 코드 리팩토링주어진 validateOrder 메서드를 리팩토링했다. Early return 패턴을 적용하고, 반복되는 로직을 Order 클래스의 메서드로 추출했다. 또한 부정 조건문을 제거하여 가독성을 향상시켰다.이렇게 접근한 이유는 다음과 같다:Early return으로 코드의 depth를 줄여 가독성을 높였다.Order 클래스에 메서드를 추가하여 객체의 책임을 명확히 하고 캡슐화를 강화했다.부정 조건문을 제거하여 코드의 의도를 더 명확히 했다.리팩토링 과정에서 가장 어려웠던 점은 적절한 메서드명을 짓는 것이었다. hasNoItems(), isInvalidTotalPrice() 등의 이름을 고민하는 데 시간이 꽤 걸렸다. 하지만 이런 고민이 결국 코드의 가독성을 높인다는 것을 깨달았다. SOLID 원칙 정리SOLID 원칙을 정리하면서, 각 원칙이 왜 중요한지 깊이 생각해볼 수 있었다. 특히 DIP(의존성 역전 원칙)가 어떻게 코드의 유연성을 높이는지 이해하는데 시간이 좀 걸렸다. 실제 프로젝트에서 이 원칙들을 적용해보면 코드의 구조가 훨씬 개선될 것 같다는 생각이 들었다. 정리이번 미션을 통해 추상화의 개념을 실제로 적용해보고, 코드 리팩토링의 중요성을 체감할 수 있었다. SOLID 원칙을 정리하면서 객체 지향 설계의 핵심을 이해하게 되었다. 앞으로는 이런 원칙들을 항상 염두에 두고 코드를 작성해야겠다는 생각이 들었다. 다만, 이론적으로 아는 것과 실제로 적용하는 것은 큰 차이가 있을 것 같다. 앞으로 더 많은 연습이 필요할 것 같다.다음 주에는 이번 주 내용도 다시 복습하면서 강의 내용을 더 깔끔하게 정리해 봐야 겠다.

회고1주차

dlckswls111

[워밍업 클럽 스터디 2기] 1주차 발자국

1주차 발자국 미션 정리 Readable Code: 읽기 좋은 코드(인프런, 박우빈) - 한 주간 배운 내용 요약하기- 추상: 추상이란 중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버린다는 것을 의미한다. 추상화의 과정에 있어 그 정도, 즉 추상화의 레벨이 일정해야 가독성이 있는 코드를 작성할 수 있다는 것을 배울 수 있었다.- 논리, 사고의 흐름 : 개발자는 여러가지 정보들을 생각하면서 코드를 짜야한다. 계속 생각해야 하는 정보들을 가능한 한 줄이며 개발을 진행해야 추후 개발에 있어서 실수를 안하게 된다는 것을 배울 수 있었다. - 객체 지향 패러다임 : 객체지향의 5대원칙이라고 불리는 SOLID에 대해 정리할 수 있었다. 또한 기존 코드를 리팩토링 하면서, 높은 응집도와 낮은 결합도를 어떻게 구현할 수 있는지 실습할 수 있어 좋은 시간이었던 것 같다. - 객체지향의 적용하기 : 코드 레벨에서 객체지향 원칙을 지키기 위한 방법에 대해 학습했다. 예를 들면 상속과 조합, 일급 컬렉션, 다형성을 활용한 코드 리팩토링 등을 진행하였고, 코드를 바탕으로 학습한 내용을 적용해보니 더욱 좋았던 것 같다. - 미션 해결 과정day2 미션 : 강의 내용에서의 추상과 구체에 대한 나만의 예시를 생각하는 미션이었다. 최근에 카카오 로그인을 구현한 경험이 있어서 '카카오 로그인하기'라는 추상을 구현했던 과정을 바탕으로 구체적으로 작성하였다.  day4 미션: 제시된 코드를 강의에서 배운 내용대로 리팩토링하는 미션이었다. 불필요한 조건문이 보였고, 이를 수정하였고, 클래스를 따로 만들어서 데이터의 직접적인 접근을 해당 객체에 위임하였다. 그리고 추상화 레벨을 유지하면서 적절하게 메서드를 구성하였다. - 회고- 강의 : 많이 타이트 했던 것 같다. 하루에 2~3시간 분량의 진도표를 따라갔는데, 프로젝트와 병행하니 시간 분배가 어려웠다. 띄엄띄엄 수강하다보니 집중이 덜 되었기에 다음주부터는 최대한 시간을 확보하여 한 챕터를 온전하게 다 들을 수 있도록 하거나 해야할 것 같다.- 미션 : 미션 해결 과정이 어려운 편은 아니었으나, 커뮤니티에 올라온 다른 분들의 다양한 접근 방법을 살펴볼 수 있어 좋았던 것 같다.

백엔드

ssun

[인프런 워밍업 클럽 2기] 백엔드 프로젝트(Kotlin, Spring) 1주차 👣

📌 강의 정보입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기(정보근) 📌 강의 수강  [웹 서비스를 구성하는 요소]클라이언트서버데이터베이스DNS 서버와 IP 주소[웹 프레임워크와 Spring]프레임워크Java, Kotlin : SpringJavaScript, TypeScript : Express.js, Nest.jsMVC 패턴(Model-View-Controller스프링 Bean과 의존성주입(Dependency Injection)생성자(constructor)수정자(setter)필드(field)[HTTP와 REST API]RequestResponseHTTP 요청 메서드GET : READPOST : CREATEPUT, PATCH : UPDATEDELETEHTTP 상태 코드200 : OK300 : Multiple Choices400 : Bad Request500 : Internal Server ErrorJSON : JavaScript Object Notation[JPA]ORM트랜잭션영속성 컨텍스트[Git과 Github]CommitRollbackBranchMergeConflictRepositoryPush.gitignorehttps://www.toptal.com/developers/gitignore[Spring boot initializr]Spring Initializr 📌 미션💡 [미션1] 테이블 설계하기팀 프로젝트에서 테이블 설계를 할 때가 생각났다. 테이블 설계를 하는 일은 재미있으면서도 데이터를 어떻게 저장하는 게 효율적일까?라는 고민들에 머리 아픈 시간을 보내기도 한다. 하지만 고민보다는 고! 설계 시 아무리 고민해도 마음에 안 든다면 일단 내가 생각한 대로 진행하고 하다가 오! 이렇게 하는 거 별로인데? 하면 바꾸면 된다.😉 (개인 프로젝트여서 가능한 생각..)내가 미션 테이블을 설계하면서 고민한 부분은 북마크 테이블이었다. 북마크 레시피 글과 밀크티샵 글의 북마크를 하나의 북마크 테이블에 저장하려고 했다. 카테고리에 레시피나 밀크티샵을 구분을 하고 카테고리 번호로 해당 게시글의 번호를 저장해 그 두 개의 컬럼을 이용해서 구분을 하고 싶어서 아래 사진처럼 설계를 했다. 하지만 이 북마크 부분은 아직 마음에 들지 않아서 프로젝트를 하면서 수정이 필요한 부분이라면 바로 변경할 예정이다. 또 하나 수정할 점은 밀크티샵의 주소 컬럼이다. 일단 프로젝트 초기에는 데이터를 저장한다는 생각에만 집중해서 하나의 컬럼으로 두고 좀 더 프로젝트를 구체화할 때 주소 컬럼을 상세 주소 컬럼으로 더 나눠서 작업할 예정이다. 💡 [미션2] 깃허브 리포지토리에 프로젝트 올리기~ 즐거운 README 꾸미기 ~ 어째서 밀크티 이모지가 없는 거야?.. 버블티 이모지만 나온다…😭 💡 ERD 설계 📌 회고 이번 1주 차는 정말 정말 바빴다. 시간 쪼개기를 하면서 “내가 버리는 시간이 이렇게 많았다고?” 와 “아 너무 에너지를 많이 썼다”라는 두 개의 생각이 공존하는 일주일이었다.이번 일주일 동안 이론에서 배운 것을 아직 다 내 것으로 만들지 못했다. 물론! 한 번만 보고는 절대 안 된다는 것을 아니까 차근차근 하자. 라고 마음먹긴 했지만 마음이 급하다… 다음 주까지는 바쁠 것 같은데 화이팅 해보자. 역시 실습을 즐거워.. 프로젝트 미리 보기를 해서 어떤 페이지들로 구성이 되는지 결과를 미리 보니까 빨리 완성해서 이곳저곳 꾸미고 구성을 어떻게 해볼까 계속 아이디어가 떠오른다. 포트폴리오를 완성하는 날까지 아자! 💪 

백엔드인프런워밍업코틀린

도호

[인프런워밍업클럽2기] 1주차 발자국

본 회고는 인프런 박우빈 지식공유자님의 Readable Code: 읽기 좋은 코드를 작성하는 사고법 강의를 듣고 작성된 내용입니다.총평 예상했던 것 보다 하루에 들어야하는 강의 분량이 많아서 힘들었다. 하지만 실무를 하면서 고민했던 부분들이 조금이나마 해결이 된 것에 보람을 느꼈다.그리고 영어 공부를 할 필요성을 느꼈다. Readable하기 위해서 중요한 것은 네이밍이라고 생각한다. 아무리 열심히 추상화를 하고 결합도를 낮춰도 변수와 메서드 이름이 의도를 담지 못한다면 의미가 없는 것 이라고 생각한다.이 생각을 한 이유는 강의를 보면서 지식 공유자님의 네이밍에 무릎을 탁 쳤기 때문이다. 나였다면 어떻게 했을까를 생각해보니 너무 초라했다. KPT 회고 Keep강의를 들으면서 나에게 필요했던, 내가 고민 했던 부분들을 위주로 정리해 나갔다.코드를 따라가면서 나라면 어떻게 수정 했을 지 고민하고 비교해보았다.Problem미션을 위한 정도까지만 강의를 듣고 DAY 5의 분량을 수강하지 못하였다. 한번 미루니 분량이 걷잡을 수 없이 늘어나버렸다. Try강의를 미리 들어놔야겠다.학습 내용 요약 읽기 좋은, 가독성이 좋은 코드를 짜야하는 이유는 무엇일까? 나는 코드를 읽을 때 생각없이 읽혀야한다고 생각한다. 문제 해결을 위해서 혹은 기능 수정을 위해서 기존 코드를 읽을 때 코드를 잘못 이해하게 된다면 잘 동작하던 부분에도 문제가 생길 수 있다. 그리고 우리의 개발 기간이 배로 늘어날 것이다. 어찌되었던 개발자는 회사의 제품을 잘 만들어서 그로 인해 회사에 수익을 가져다줘야하는 역할을 맡고 있기 때문이다. 개발 기간이 줄어들면 그만큼 투입되는 인건비가 줄어드는 것 일 테니까 말이다. 읽기 좋은 코드를 짜기 위해서는 아래 항목들에 신경을 써야한다.네이밍추상화Depth 그리고 공백 즉 비즈니스가 담고있는 내용을 쉽게 읽히게 해야한다.객체 지향 코드가 무조건 가독성이 좋고 어느 상황에서든 적용되어야하는 패러다임은 아니다. 상황에 따라서 절차 지향 코드가 더 적절할 때도 있을테니까.그렇지만 알고서 안쓰는거랑 모르고 안쓰는 것, 잘못알고 잘못 쓰는 것은 다르다. 강의에서는 객체 지향 패러다임에 대해서 여러 세션을 할당해서 설명하고 있다.흔히들 객체지향에서 이야기하는 SOLID, 캡추상다 같은 특징들은 구글 검색만 해도 나오니까 넘어가도록 하겠다.미션 회고이번 주차의 미션은 실생활의 행동을 추상화, 코드를 리팩토링하고 SOLID를 정의하기 였다.정의와 추상화는 어렵지 않았지만 코드 리팩토링은 약간의 고민이 필요했다.이유는 유효성 검증을 ealry return할 때, Order의 사용자가 유효한지를 Order가 검사하는 것이 맞을까? 라는 고민을 했었다.나의 결론은 Order도 User의 정보를 갖고있기 때문에 Order 레벨에서 User에 대한 검사정도는 필요하다고 판단했다. User가 null일 수도 있으니까 말이다.다음 미션은 리팩토링해보기인데 정말 기대된다. 아자아자

인프런워밍업클럽2기readablecode박우빈

종운

읽기 좋은 코드를 작성하는 사고법

강의 돌아보기스터디 1주차에 접어들면서 내가 오해하고 있었던 부분들과 모호한 부분들을 맞춰 갈 수 있는 주차였다.요즈음에는 어떤 지식을 습득하고 적용을 하기 위해서 가장 좋은 방법이 무엇일까에 대해서 고민을 하고 있다.그 와중에 마인드 맵으로 카드 형태로 정리하는 방식이 내가 공부한 지식을 나만의 방식대로 기억 할 수 있게 해주는 좋은 도구가 아닐까 하여 옵시디언을 사용하였다.누군가는 이것이 무엇을 의미할까? 라는 생각이 들 수 있다.하지만 확실히 카드 형태로 공부한 내용을 정리했을 때 해당 키워드를 바라보면 그때 공부했던 그 장면이 머릿속에 재생된다.하지만, 아직까지는 마인드 맵에 너무 많은 세부 내용들이 노출되고 있다라는 느낌이 든다. 세부내용을 "별도의 페이지를 만들어서 노출시키는 것은 어떨까?" 라는 생각이 든다. 이것 또한 추상화가 아닐까.핵심특히, 가장 코드를 짜면서 헉! 했던 강의 파트는 추상화 레벨이다.누군가의 코드를 보다보면 뭔가 어색하다라고 느껴지는 상황이 있다. 그리고 그 상황이 바로 추상화 레벨이 다르기 때문이 벌어진 일이었다.어딘가는 외부 세계를 만들어서 객체로 처리하고 있는데 어딘가에는 private method로 밖에서 처리하고, 어딘가는 로직의 구현체가 내부에 있다보니 내가 어떤 방향으로 코드를 읽고 따라가야 하는지에 대한 방향을 잃었다고 생각이 든다.미션 회고1주차에는 두 가지 미션이 있었다. 추상을 구체화 하기코드를 적절한 추상화 레벨에 맞춰 리팩토링 하기추상을 구체화 하기미션을 수행하면서 최근에 굉장히 중요하다고 생각하고 있는 CS 지식을 얼마나 잘 알고 있는가. 그리고 실제로 나는 어떠한 사고를 가지고 있는가를 판단하기 위해 미션에 접목했다.세상에 모든 일은 추상화 되어있다고 생각한다. 그리고 고수준 레벨의 언어, 프레임워크가 잘 만들어지게 되면서 이제는 코드를 짤 때 내부의 구체를 갈수록 보기 어려운 형태가 되어가고 있다.과연 어떠한 우리가 웹에서 어떠한 작업을 할 때 얼마나 많은 일들이 발생하고 빨리 처리되고 있는 것일까? 를 기준으로 수행했다.하지만, 추상화 레벨을 너무 높게 잡아서 였을까? 추상화에 1뎁스 구체 -> 2뎁스 구체 -> 3뎁스 구체 너무 깊은 레벨의 구체까지 바라보게 되는 느낌이 있었다. "코드의 컴파일 단계까지 적어야하나?" 라는 생각도 들었는데, 그건 너무 깊게 갔다고 생각하여 해당 구체 레벨까지 내려가지는 않았다.이 미션을 수행하면서도 사람이 읽기 좋도록 추상화 레벨을 신경쓰는 것이 좋다. 라는 생각이 한편으로 들었다.추상화 레벨에 맞춰 리팩토링 하기미션을 보고 가장 먼저 눈이 갔던 부분은 boolean 타입의 반환 타입이었다. "객체의 상태 검증을 외부로 노출하는 것이 맞는 것일까?", "이 객체는 어떻게 존재 할 수 있지?" 를 관점으로 코드를 바라봤다. 특히, 경험적인 부분이 많이 반영이 되기도 했다.회사 내에서 금액은 보통 대부분의 도메인이 회사 내부적으로 금액을 우리가 어떻게 처리 할 것인가로 처리를 하게 되는 상황이 많았다. 예를 들어, 소수점은 없앤다거나, 100원 단위는 절삭한다. 같은 비즈니스 요구사항이 다음과 같은 예이다.적절하게 책임을 가질 수 있는 객체로 분리하고 코드를 작성했다. 미션에서 검증해야 할 요구사항은 객체의 생명주기에 따른 사전 조건에서 검증을 해야 된다고 생각하고 생성자에서 검증을 하도록 하였다.하지만, 그 부분에서 궁금증이 생긴 부분이 있었다. "어떠한 경우에는 객체에게 지금 나의 상태에 대해서 물어 볼 상황이 생길 수도 있지 않을까? 그렇다면, 그 경우는 언제지?"명확한 기준이 잡혀있지 않았고, 해당 내용을 우빈님에게 질문을 하게 되었다.다음과 같은 내용으로 질문을 드렸고, 우빈님께서 '유효성 검증', '상태 확인'의 개념을 분리하는 것이 좋을 것 같다는 피드백을 주셨습니다. 말씀을 듣고 보니 결국 이것도 비즈니스 요구사항에 따라서 도메인의 생명 주기가 어떻게 이루어져야하는가에 따라서 달라질 수 있다는 결론을 내렸습니다.아쉬운 점totalPrice를 계산하는 부분을 메서드로 호출 할 때마다 연산하는 형태로 구현을 해두었습니다.return orderItems.stream() .map(OrderItem::getMoney) .reduce(Money.from(BigDecimal.ZERO), Money::add)호출 할 때마다 매번 연산 로직이 수행 될 것입니다. 추후에는 totalPrice가 coupon에 적용되는 비율이 달라지는 등 여러 형태로도 사용 될 수 있겠다는 생각이 들었어요. 아래와 같은 방법으로 하는 것은 어땠을까? 라는 생각이 들었습니다.private Order(List<OrderItem> orderItems, ...) { this.totalPrice = calcTotalPrice(orderItems); } private Money calcTotalPrice(List<OrderItem> orderItems) { return orderItems.stream() .map(OrderItem::getMoney) .reduce(Money.from(BigDecimal.ZERO), Money::add) }위와 같이 작성한다면 객체의 생성 시점에 한 번 totalPrice를 생성하고 여러 방면으로 재활용 할 수 있지 않을까 라는 생각이 들었습니다.결론다음 미션도 너무나도 기대되고, 해당 스터디를 하게 되어 정말 다행이라고 생각하고 있습니다.한 가지 아쉬운 점은 스터디의 의존도가 멘토 분들에게 많이 의존되어 있다는 생각이 들었습니다. 같이 스터디를 하는 많은 분들이 많은 의견을 공유 할 수 있는 환경이 되었으면 좋겠다? 라는 개인적인 아쉬움이 조금 있었네요.

백엔드발자국클린코드

[워밍업 클럽 스터디 2기 :: BE 클린코드 & 테스트] 1주차 발자국

1주차에서 배운 내용 중 가장 중요한 주제는 추상화라고 생각한다.섹션 2 추론에서는 프로그램은 데이터와 코드의 조합으로 구성되어 있고 코드 설계 시 중요한 정보는 남겨 데이터를 관리하는 코드에서 중요한 말만을 남겨 놓는다. 이렇게 추상화를 할 때 높은 추상화와 낮은 추상화가 격으로 나눠지는데 클린 코드를 함에 있어 main 부분에는 고수준 추상화를 남겨 놓구 구현부에 저수준 추상화를 하여 동료 개발자가 유지보수하기 편하게 약속을 정할 수 있다.결국, 구체로 부터 추상을 해내고 정보를 덜어내고 가장 중요한 정보는 남기고 추상화 과정은 컴퓨터 과학의 본질이고 도메인 개념에서 추상화 하는 작업을 해나가야 한다.. 섹션3 논리, 사고의 흐름에서는 Early return 으로 else의 사용추상화를 통한 사고 과정의 depth를 줄이는 것이 중요공백으로 문단 나누자코드(조건문 등)에서 부정어구(!)를 가급적 피하자예외 발생 중 NPE에 대한 부분을 처리하는데 있어 Optional에 대해 배웠음orElseThrow() : 선택적 값이 존재하지 않으면 NoSuchElementException 같은 예외를 발생시키며, 존재하면 그 값을 반환orElse() : 선택적 값이 없으면 괄호 안에 정의된 기본 값을 반환orElseGet() : 선택적 값이 없으면, orElseGet()에 전달된 람다식이나 메서드가 실행되어 그 값을 반환섹션 4 객체 지향 패러다임에서는 SOLID에 대해서 배웠다.1. SRP(Single Responsibility Principle)하나의 클래스는 단 한가지의 책임(변경 이유)만을 가져야 함2. OCP (Open-Closed Principle)확장에는 열려있고 수정에는 닫혀 있어야 함기존 코드 변경 없이 시스템의 기능을 확장할 수 있는 코드로 실무에서 구현LSP (Liskov Subsitution Principle)상속 구조에서 자식 클래스는 책임을 담당하고 부모 클래스의 행동을 변경하지 않아야 함ISP ( Interface Segregation Principle)인터페이스 스펙을 분리하여 구현체가 불필요한 스펙까지 상속하지 않아야 함인터페이스가 여러 기능을 포함하고 있으면 결합도가 증가인터페이스를 분리구현체가 분리된 인터페이스를 상속하여 사용하고자하는 스펙만을 사용하도록 함DIP (Dependency Inversion Principle)고수준 모듈은 저수준 모듈을 직접적으로 의존하기 보단 중간에 인터페이스를 생성하고 인터페이스에 의존하도록 함고수준 : 추상화 레벨이 높은 것저수준 : 추상화 레벨이 높은 것의존성 순방향 : 고수준 모듈이 저수준 모듈을 참조의존성 : 하나의 모듈이 다른 하나의 모듈을 알고 있거나 직접적으로 생성하거나 사용하는 모든 것해당 내용을 배웠다.

백효석

인프런 워밍업 스터디 클럽 2기 백엔드(클린코드, 테스트코드) 1주차 발자국

강의 수강강의 요약추상과 구체에 대한 개념을 학습하고 내가 작성한 코드를 다른 사람들이 봤을 때 읽히기 쉽도록, 즉 가독성이 좋게 하기 위한 방법들에 대해 학습했다.코드를 읽는 과정에서 사고의 depth를 줄이기 위한 추상화 레벨 고려, 부정어구 사용, 변수명 고려 등을 학습하였다. 코드의 설계가 잘 되어있지 않으면 후에 코드가 더 무거워지고 복잡해지면 기능의 추가, 삭제 및 변경 시 수많은 문제에 시달리게 될 것이다. 따라서 SOLID 라는 객체지향 5원칙을 준수하며 코드를 설계하면 이러한 문제를 줄일 수 있게되고 이 SOLID에 대해 학습하고 코드의 전반적인 리팩토링 과정을 학습했다. 일주일 회고아쉬웠던 점과 추후 목표섹션 1~3까지의 내용은 지금까지 코드를 작성한 나에게 있어서도 변수명에 대한 고민부터 시작해 사고의 depth를 줄이기 위한 고민, 추상화를 위한 고민 등 지금까지 작성했던 코드를 보며 어떤식으로 리팩토링을 시킬지 감이 잡혔지만 SOLID를 시작으로 Value Object, 일급 컬렉션 등 익숙하지 않은 내용으로 그저 강사님의 코드를 따라치기만 하며 나의 코드에서 해당 개념을 어떻게 적용시킬지 감이 잡히지 않는 모습을 보며 추가적인 공부와 경험이 필요하겠다는 생각이 들었습니다. 미션[Day2] 추상과 구체 구체로부터 정보를 덜어내고 가장 중요한 정보는 남기는 추상화 과정을 통해 추상을 하게 된다.추상과 구체를 보여주기 위한 예시로 방 청소를 하는 행위를 추상으로 두고 해당 추상을 구체화 시켜봤다.바닥에 놓여진 물건들을 치운다.빗자루를 들고와 바닥을 쓴다.물티슈를 가져와 바닥을 닦는다.방 청소를 하는 과정에서 내가 무엇을 가지고 어떠한 청소를 했는지, 자세한 내용은 모두 덜어내고 가장 중요한 정보인 내가 방 청소를 했다. 라는 내용만 보여주면 되므로 추상을 방 청소를 한다 로 설정했다. [Day4] 읽기 좋은 코드로 리팩토링 및 SOLID코드 리팩토링public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; }사용자가 생성한 주문이 유효한지를 검증하는 메서드로 강의에서 배운 내용을 활용해 가독성을 높이는 작업을 했다.먼저, 검증 메서드를 실행시키기 위해 주문하는 사람, 물건, 주문 객체를 모두 생성해줬다. 새로운 주문에 대한 검증과정은 주문시 생성된 Order 객체 자신이 가장 잘 알 것이라고 판단해 validateOrder() 메서드를 Order 클래스 내에 넣어줬다.public class Order { private static final Logger log = Logger.getGlobal(); private final Customer customer; private final List<Item> orderItems; public Order(Customer customer, List<Item> orderItems) { this.customer = customer; this.orderItems = orderItems; } public boolean validateOrder() { // 1번 및 2번 if (orderItems.isEmpty()) { log.info("주문 항목이 없습니다"); return false; } // 3번 if (isNotAvailablePrice()) { log.info("올바르지 않은 총 가격입니다"); return false; } // 4번 if (hasNotCustomerInfo()) { log.info("사용자 정보가 없습니다"); return false; } return true; } private boolean isNotAvailablePrice() { return getTotalPrice() <= 0; } private int getTotalPrice() { int totalPrice = 0; for (Item item : orderItems) totalPrice += item.getPrice(); return totalPrice; } private boolean hasNotCustomerInfo() { return customer == null; } }기존의 코드에서 가독성을 높이기 위해사고의 depth를 줄이기 위한 중첩 분기문 분리 및 공백 라인 추가메서드명을 보고 무엇을 의미하는 메서드인지 이해하기 쉽도록 메서드명 변경두 번 이상의 사고를 줄이기 위한 부정어 제거의 고려사항을 두고 코드를 리팩토링 해주고 비교했을 때 확실히 기존의 코드보다 리팩토링 과정을 거친 코드를 읽을 때 머리가 편하다는 느낌을 받았다. SOLID소프트웨어 개발에서 좋은 설계를 만들기 위한 5가지 기본 원칙으로 나만의 언어로 바꿔 설명하기 위해 레스토랑에서 음식을 준비하고 서빙하는 과정으로 SOLID 원칙을 설명했다.SRP, 단일 책임 원칙레스토랑에서 주방장이 요리만 하고, 서빙 직원이 손님에게 음식을 가져다 주는 것과 같다. 주방장이 요리도 하고 서빙도 하면 효율이 떨어지게된다. 따라서 주방장은 요리만, 서빙 직원은 서빙만 하게 하면 각자 맡은 일에 집중할 수 있다.  OCP, 개방-폐쇄 원칙레스토랑에서 새로운 메뉴가 생겼을 때, 기존의 주방 시스템을 바꾸지 않고 메뉴판에만 새로운 메뉴를 추가하는 것처럼 주방 시스템이 잘 되어 있다면 추가된 메뉴도 쉽게 조리할 수 있다.LSP, 리스코프 치환 원칙레스토랑에서 일하는 직원들이 모두 손님에게 서비스를 제공해야 한다. 서빙 직원이나 매니저, 혹은 주방 보조 등 누구든지 필요할 때 손님을 도울 수 있어야 한다(계산, 주문 등등). 즉, 어떤 역할이든 기본적으로 손님을 응대하는 일은 모두 잘할 수 있어야 한다.ISP, 인터페이스 분리 원칙주방장은 요리하는 방법만 알면 되고, 서빙 직원은 손님에게 음식을 가져다주는 방법만 알면 된다. 주방장이 서빙 절차를 알 필요는 없고, 서빙 직원이 요리하는 방법을 알 필요도 없다. 각자 필요한 일만 집중해서 하면 된다.DIP, 의존성 역전 원칙레스토랑에서 주방장이 어떤 요리 도구 (가스레인지, 조리 도구)를 사용하든 상관없이, 요리만 잘 하면 상관이없다. 즉, 주방장(고수준 모듈)은 구체적인 도구(저수준 모듈)에 의존하지 않고 요리 도구라는 추상적인 개념에 의존하는 것이다. 그래야 나중에 도구가 바뀌어도 주방장은 문제없이 요리할 수 있다.

백엔드백엔드클린코드

인프런 워밍업 클럽 2기 백엔드 클린 코드와 테스트 코드 1주 차 발자국

이번주 회고첫 번째 주 인프런 워밍업 클럽2기 클린코드와 테스트를 진행하였습니다.이어진 연휴로 인한 게으름과 사옥 이전으로 인한 스트레스 등으로 일부 완강하지 못한 섹션이 있어서 다소 아쉬움이 남습니다. 하지만 그래도 꾸준히 들으려고 했다는 점과 뭐라도 시도했다는 점은 칭찬할 점인 것 같습니다.다음 주부터는 이제 안정되어가는 시기이니, 휴일에 밀린 섹션을 듣고 다시 열심히 해보겠습니다. 강의를 수강하면서 꼭 기록하고 싶은 내용부정어를 대하는 자세if(!isRightDirection()) 을 사용하는 것보다는 if(isNotRightDirection())을 사용하는 것이 더 읽기 쉽다. 관심사의 분리 (Seperation Of Concern)추상화 작업 중 하나로, 일련의 작업들을 묶어서 이름을 짓고, 역할을 분리하여 높은 응집도와 낮은 결합도를 지니게 하는 것이다. 이를 통해 우리는 유지 보수를 좀 더 수월하게 할 수 있게 된다. 객체로 추상화하기공개 메서드 선언 부를 통해 비공개 필드(데이터)와 비공개 로직(코드)를 외부 세계와 소통할 수 있게 구현한다. 새로운 객체를 만들 때 고려해야할 점1개의 관심사로 명확하게 책임을 정의해야 한다.데이터는 변하지 않는 것이 제일 좋다.setter 지양 : 객체 내부에서 외부 세계의 개입 없이 자체적으로 변경을 처리할 수 있어야 한다.getter 지양 : 객체에 메세지를 보내는 것이 좋다. 외부에서 getter를 남발하는 것은 캡슐화를 한 의미를 져버리는 것이다. 미션 이번 미션의 경우, 간단한 리팩토링 미션이었습니다. 강의에서 들은 것을 바탕으로 도메인 안에서 객체에 대한 로직을 처리하도록 구현하여 응집도를 높였고 캡슐화를 진행하였습니다. 객체 내에서도 List를 사용하는 객체는 일급 객체로 구현하여 유지 보수에 용이하게끔 구현하였습니다.또한 부정어도 최대한 가독성이 높아지도록 Not을 붙여서 구현하였습니다.

V_브이_v

[인프런 워밍업 클럽 스터디 2기] : 웹 개발의 첫 발자국

안녕하세요!제 첫 블로그에서는 자바 스프링(Spring Framework)을 처음 배우면서 느꼈던 점과1주차에 배운 주요 개념들을 간단하게 정리해보려고 합니다.새로운 것을 배운다는 생각에 약간 신나기도 합니다 ㅎ이번 스프링 스터디를 통해 스프링을 배우며 얻은 지식을 꾸준히 정리해 블로그에 올리는 습관을 만들어보겠습니다. 간단한 웹 이론웹 서비스는 크게 세 가지 요소로 구성됩니다: 클라이언트, 서버, 데이터베이스.- 클라이언트: 요청을 보내는 주체로, 컴퓨터나 스마트폰의 브라우저 등 사용자가 접근하는 환경입니다.- 서버: 클라이언트의 요청을 받아 작업을 수행하는 주체입니다.- 데이터베이스(DB): 데이터의 집합으로, DBMS를 통해 데이터를 관리합니다.  웹 프레임워크와 라이브러리의 차이웹 프레임워크: 웹 개발을 편리하게 도와주는 도구로, 이미 정해진 틀이 있습니다.예를 들어 이케아의 가구처럼 정해진 틀과 구성이 있다고 생각할 수 있습니다.라이브러리: 정해진 틀이 없는 도구로, 필요에 따라 사용할 수 있는 철물점의 도구와 비슷합니다. 스프링 프레임워크스프링은 자바로 만들어진 웹 프레임워크로, 다양한 주요 개념이 있습니다.MVC 패턴MVC 패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 소프트웨어 아키텍처 디자인 패턴입니다.- 모델: 데이터를 담는 역할을 합니다.- 컨트롤러: 클라이언트의 요청을 받아 작업을 수행하고, 결과 데이터를 모델에 담습니다.- 뷰: 사용자에게 보여지는 화면으로, 모델에서 데이터를 가져와 표시합니다. 레이어드 아키텍처- 컨트롤러: 클라이언트의 요청을 받는 인터페이스로, 데이터를 검증하고 서비스의 메소드를 호출합니다.- 서비스: 데이터 처리 로직을 수행하고, 저장소(DB)에 대한 작업을 요청합니다.- 저장소: DB에 접근하여 데이터를 처리하며, 여러 서비스에서 공통적으로 사용할 수 있는 처리 방법을 제공합니다. 스프링 빈(Spring Bean)스프링에서 관리하는 자바 객체를 빈(Bean)이라고 합니다. 스프링 빈은 스프링 컨테이너에서 생성되고 관리되며, 다음과 같은 특징이 있습니다.- 관리되는 객체: 스프링 컨테이너에서 생성되고 관리됩니다.- 재사용 가능: 애플리케이션 전체에서 재사용이 가능합니다.- 의존성 주입: 스프링이 빈들 간의 관계를 자동으로 설정해줍니다. 스프링 부트에서의 의존성 주입- 생성자 주입: 클래스 생성자를 사용하는 방식으로 가장 안전합니다.- 수정자 주입: @Autowired 같은 어노테이션을 사용하여 필드를 주입하는 방식입니다.- 필드 주입: 수정자 주입과 비슷하게 동작하지만, 의존성을 바꿀 때 문제가 생길 수 있어 신중히 사용해야 합니다. HTTP와 REST APIHTTP 메소드- GET: 리소스를 가져오는 요청- POST: 리소스를 생성하는 요청- PUT/PATCH: 리소스를 업데이트하는 요청- DELETE: 리소스를 삭제하는 요청- 오류 코드: 400번대는 클라이언트의 문제, 500번대는 서버의 문제를 나타냅니다.REST API: HTTP 통신을 통해 애플리케이션 기능을 정의하는 규칙입니다.URL을 자원으로 활용하고, HTTP 메소드를 이용해 행위를 표현합니다. JPA (Java Persistence API)JPA는 자바 객체를 DB의 테이블로 매핑해주는 기술입니다. 이를 통해 개발자는 직접 쿼리를 작성하지 않고도 객체지향적인 접근이 가능합니다.- 장점: 생산성 증가, 객체지향적 접근, DBMS 의존성 감소- 단점: 충분한 학습이 필요하고, 복잡한 쿼리를 처리하는 데 한계가 있을 수 있습니다. 느낀 점과 회고스프링을 처음 배우며 새로운 개념들을 많이 접했습니다.특히 빈과 의존성 주입 개념이 이해되면서도 어려우면서도 아리까리?합니다.직접 프로젝트에 적용해보면서 개념을 익히는 것이 가장 좋겠다고 생각했습니다.기본적인 웹 이론에 대해 잘 모르고 있던 부분이 많아 부끄럽기도 했지만, 이번 기회를 통해 기초부터 다시 다질 수 있어서 좋았습니다.앞으로도 더 많은 기능을 학습하고 블로그에 정리하며 성장해 나가고자 합니다.오늘 남은 시간을 활용해서 섹션 3의 진도를 나가고 최종장에는 개인프로젝트를 실제로 동작하게끔 만들고 싶습니다!!

백엔드javaspring스터디미션발자국백엔드

인프런 워밍업 클럽 2기 - 백엔드 프로젝트(Kotlin, Spring) - 발자국 1주차

학습 내용이번 주차는 Spring Boot 프레임워크에 대한 대략적인 이해와 프로젝트의 구성, 그리고 ERD 모델링 등에 대해서 학습하였습니다.SpringBoot가 아닌 다른 백엔드 프레임워크를 사용해본 경험이 있어 이번 주차는 크게 어렵게 느껴지지 않았지만, Spring Initializer/Intellij를 사용하는 것 등 유용한 방법을 배울 수 있어서 좋았습니다미션미션1의 경우 ERD를 작성할 때 아이디어에 대해 어떻게 테이블 및 테이블 간 관계를 구성할지 고민을 하였고 (단순히 레스토랑만을 리스트업하는 것이 아니라, 리스트업 된 레스토랑 내 채식주의자가 먹을 수 있는 메뉴만을 구성하는 것), 해당 문제를 Restaurant / Menu 간 일대다 관계, 그리고 Restaurant - User 다대다 관계 (Comment/Like)를 형성함으로서 해결하였습니다.미션2의 경우 스프링 프로젝트를 강의에서 알려주신 대로 Spring Initializer와 Intellij를 이용하여 셋업하고, 이를 깃허브 레포지토리에 업로드하는 것이었습니다.미션 1의 경우 시행착오와 계획 수정이 조금 필요했고, 미션2의 경우 강의를 따라가며 무리 없이 마무리할 수 있었습니다.참조 강의입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기https://www.inflearn.com/course/%EC%9E%85%EB%AC%B8%EC%9E%90-spring-boot-kotlin-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4/dashboard

taeminseo

워밍업 클럽 스터디 2기 [클린코드 & 테스트코드] - 1주차 회고

1주차 회고1. 추상중요한 정보를 잘 전달하고 전달하지 않아도 되는 정보는 생략하여 간결하게 만듬내 코드를 다른 사람이 알기 쉽도록 메서드명을 명확하게 지어냄코드 레벨에서 추상화 레벨을 일관적으로 유지메서드 선언부를 void 대신 반환할 값이 있는지 고민하여 테스트시 용이할 수 있도록 반환값 부여매직 넘버 , 매직 스트링을 상수로 추출하여 가독성 향상추상과 구체에 관한 미션2. 논리 , 사고의 흐름early return을 사용하여 else , else-if 사용을 지양추상화를 통한 사고과정의 depth를 줄이기. (중첩 분기분 , 반복문이 사고하는것에 도움이 된다면 그대로 두는것도 좋음)공백라인을 사용해 의미단위를 끊어서 정보전달이 수월하게 함부정어는 읽는사람을 한번더 생각하게 만들기 때문에 부정어를 사용하는 것을 지양의도한 예외와 의도치 않은 예외를 구분하고 커스텀 exception과 exception 클래스를 정의하고 exception handler를 만들어 처리optional orElse , orElseGet , orElseThrow3. 객체 지향 패러다임무분별한 getter 사용 자제 - getter 사용하기 이전 객체에게 메세지를 보내는 방법 고려setter 사용 자제 SOLID + 코드 리팩토링 미션 진도를 정해진 하루 양보다 다 따라잡지 못했고 정리하면서 듣지 않고 흘러가는대로 듣기만 한거 같아서 아쉬웠다. 강의와 미션에서 배운 것을 실무에서 직접 프로젝트를 만들때 한번 더 고려하고 생각해봐야 겠다.  

백엔드

채널톡 아이콘