티스토리 서버가 터진 이후 한동안 노션으로 스터디 기록을 했습니다. 

사실 아래보다 더 끄적끄적댄 것들이 많은데 일단 여기까지만 한 번.. 공유 해봅니다아..

 

|  노션 북스터디 기록

 

1. 운영체제 곰책 정리

https://simdev1234.notion.site/3c35815411cf42fc8911bb985a435f47

 

운영체제

- 강의 링크 : http://www.kocw.net/home/cview.do?cid=3646706b4347ef09

simdev1234.notion.site

 

2. 클린코드 

https://simdev1234.notion.site/78ad4b26ee2040d4aa4fd0238480b4ca

 

클린코드

A new tool for teams & individuals that blends everyday work apps into one.

simdev1234.notion.site

 

3. 이펙티브 자바 (뒷부분은 너무 귀찮아 정리를 하진... 않았지요..)

https://simdev1234.notion.site/1f185b9eccc3486dabde2bacab6c7c13

 

이펙티브 자바

하위 링크

simdev1234.notion.site

 

4. 스프링부트 핵심

https://simdev1234.notion.site/5e6f03a63ccd45548c86e5518bdcb3cb

 

스프링부트 핵심가이드

A new tool for teams & individuals that blends everyday work apps into one.

simdev1234.notion.site

 

|  기술면접 요약

팀원 분들과 정말 죽도록 많이많이 연습했습니다만, 왜 면접을 가면 정작 아무도 물어보지 않는거지(배부른 소리)

https://simdev1234.notion.site/d38e000e90f94637b56e33f8c60da14a

 

기술면접 요약

참고링크 : ‣

simdev1234.notion.site

 

|  도큐먼트 모음 (이라 읽고 그냥 정리본이라 한다)

https://simdev1234.notion.site/9c741b5bf0454fedb6c92655b363d488

 

도큐먼트 모음

A new tool for teams & individuals that blends everyday work apps into one.

simdev1234.notion.site

 

|  용어

  종류 내용
오류(error) OutOfMemory Exception 시스템 상 메모리 부족
StackOverflow Exception 스택 오버플로우 발생
예외(exception) checked Exception Compile 시 체크 : type, syntax 에러
unchecked Exception Runtime 시 확인 : 다양함

 

|  올바른 예외 핸들링 방법

- 예외 핸들링 : 런타임 시에 발생하는 예외에 대한 핸들링

- 올바른 예외 핸들링이란?

(1) 예외 상황이 발생할 때, return을 하지 말고 throw를 해라

(2) try - catch 문을 통해 잡은 예외는 꼭 처리하여 사용자단에 안내한다.

(3) 예외 상황일 때만 예외를 던지고, 아닐 경우, 남발하지 않는다.

(4) 최상위 예외인 Exception만 사용하지 말고, 가능한 예외사항들을 모두 catch한다.

(5) call stack을 잃어버리지 않도록, custom exception 생성자 안에 전달한다.

(6) 너무 많은 custom exception 또한 가독성을 해치므로 지양한다.

** 추가적으로, 예외처리 외에 try문을 쓸 때는 가능하면 try-resource를 쓰는 게 낫다.

 

|  관련 배경지식

1. 스프링 부트의 디폴트 예외 처리 - BasicErrorController

- 클라이언트에서 어떤 요청사항을 전달하면, 컨트롤러에서 서비스 함수를 호출하여 로직을 실행한다.

  이 과정에서 실시간으로 비즈니스 로직상의 예외가 발생할 수 있다.

- Spring Boot의 경우, 예외가 발생하면 BasicErrorController가 디폴트인 예외 정보를 전달하는데,

  내부적으로는 ResponseEntity안에 에러 정보(Object타입)와, 상태(HttpStatus) 정보를 담아 전달한다.

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
   HttpStatus status = getStatus(request);
   if (status == HttpStatus.NO_CONTENT) {
      return new ResponseEntity<>(status);
   }
   Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
   return new ResponseEntity<>(body, status);
}

>> BasicErrorController를 통해 예외 정보를 전달할 땐 timestamp, status, error, path 정보를 전달한다.

HTTP/1.1 404 
Content-Type: application/json
Content-Language: ko-KR
Transfer-Encoding: chunked
Date: Fri, 14 Oct 2022 10:21:58 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "timestamp": "2022-10-14T10:21:58.621+00:00",
  "status": 404,
  "error": "Not Found",
  "path": "/"
}

 

2. AOP에 대한 지식

- AOP란, Aspect Oriented Programming 의 약자로,

  관점에 따라 1) 핵심, 2) 부가를 나누어 처리하는 것을 말한다.

- 일반적으로 1) 핵심은 비즈니스 로직이며,

                      2) 부가는 DB 트랜잭션 동기화 처리, 로깅, 예외처리, 파일 입출력 등이 될 수 있다.

Aspect = Advice + PointCut

// Advice : @Before, @After, @Around 가 붙은 메소드
// PointCut : AspectJ를 통해 정확한 @Target 지정
개념 정의 코드
Aspect 부가 관심사 그 자체 @Aspect
JoinPoint 클라이언트가 호출하는
모든 비즈니스 메소드

* 일종의 포인트 컷 후보
 
Target Aspect를 적용할 곳 (클래스, 메소드..)
* Annotation으로 지정
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AccountLock{

       long tryLockTime() default 5000L;

}
Advice 실제 구현 기능 @Before("AspectJ문")
@After("AspectJ문")
@AfterReturning("AspectJ문")
@AfterThrowing("AspectJ문")
@Around("AspectJ문")
PointCut 특정 조건에 의해 필터링된 조인포인트

* AspectJ 표현식 사용
더보기

** 링크 : https://github.com/simDev1234/Account/commit/1b255f1656acd998c3f72135892517823d569aa1

package com.example.account.service;

import com.example.account.aop.AccountLockIdInterface;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class LockAopAspect {

    private final LockService lockService;

    @Around("@annotation(com.example.account.aop.AccountLock) && args(request)")
    public Object aroundMethod(
            ProceedingJoinPoint pjp,
            AccountLockIdInterface request
    ) throws Throwable {
        // lock 취득 시도
        lockService.lock(request.getAccountNumber());

        try {
            return pjp.proceed();
        } finally {
            // lock 해제
            lockService.unlock(request.getAccountNumber());
        }
    }

}

- 예외를 핸들링하는 것도 AOP의 부가 Aspect에 해당된다. 

  스프링 부트에서는 @ControllerAdvice 또는 @RestControllerAdvice를 통해,

  Controller 및 RestController에서 어떠한 처리가 일어날 때마다 @ExceptionHandler에 작성한 코드를 실행한다.

  * 기본적으로 클래스 안에 @ExceptionHandler를 작성하면, 그 클래스에서 발생하는 예외 처리를 그 핸들러에서 한다.

 

|  예외 처리하기 실습 

- 일반적으로 회사에서는

  (1) GlobalExceptionHandler를 통해서 Custom Exception을 포함해 자주 사용하는 예외 및 최상위 예외를 핸들링한다.

  (2) 이 때, 반환 타입으로는 BasicErrorController와 마찬가지로 ResponseEntity를 사용할 수 있는데, 

       앞서 보았듯이 ResponseEntity는 Response의 body에 넣을 데이터와, Response Status 값을 포함한다.

       따라서, Response body에 넣어줄 객체를 따로 생성하는데, 나는 이걸 ErrorResponse로 두었다.

  (3) 비즈니스 로직에 맞는 커스텀 예외를 만들 때 가장 먼저 RuntimeException을 상속한다.

       기본적으로 RuntimeException은 String타입의 message를 포함하며, Throwable를 생성자에 넣어주어야만, throw를 할 수 있다. 자주 실수하는 부분으로 throwable를 안 넣어주면 커스텀 예외처리하는 의미가 없단다.

  (4) 커스텀 예외 맴버변수로, 추가적으로 Enum타입의 ErrorCode를 넣어줄 수 있는데, 이렇게 ErrorCode로 데이터들을 싸주는 이유는, 아무래도 서비스에서 CustomException을 throw할 때, 한 줄로 던질 수 있기 때문에서라 보인다.

  ** throw 해서 받은 ErrorCode객체 안의 데이터들은, 

    (2)의 Response body에 넣어줄 비즈니스 로직 에러에 대한 영문 코드 + 한글 message와, HttpStatus를 갖는다.

public RuntimeException(String message, Throwable cause) {
    super(message, cause);
}
  GlobalExceptionHandler ErrorResponse CustomException ErrorCode(enum)
용도 컨트롤러의 예외 발생 시
핸들링
Response body에 담을
데이터 묶음
비즈니스 로직에 맞는
사용자 정의 예외
 
상속 - - RuntimeException -
Annotation @ControllerAdvice
@RestControllerAdvice
@Builder
@Value
@Getter
@AllArgs..
@RequiredArgsCon...
@Getter
Member @ExceptionHandler
+ 사용자 정의 예외
+ 자주 나타나는 예외
+ 최상위 예외 처리
: ResponseEntity
- String code
- String message
- ErrorCode code - HttpStatus status
- String message

 

1. ErrorCode 만들기

package com.example.exceptionhandling.type;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@RequiredArgsConstructor
@Getter
public enum ErrorCode {

    MEMBER_NOT_FOUND_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "회원을 찾을 수 없습니다."),
    MEMBER_USER_ID_NOT_MATH_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR,"아이디가 일치하지 않습니다."),
    MEMBER_ALREADY_UNREGISTERED_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR,"탈퇴한 회원입니다.");

    private final HttpStatus status;
    private final String message;

}

 

2. Custom Exception 클래스 만들기

package com.example.exceptionhandling.exception;

import com.example.exceptionhandling.type.ErrorCode;
import lombok.Getter;

@Getter
public class MemberException extends RuntimeException{

    private final ErrorCode code;

    public MemberException(ErrorCode code) {
        super(code.getMessage());
        this.code = code;
    }

}

 

3. ErrorResponse 만들기

package com.example.exceptionhandling.exception;

import lombok.Builder;
import lombok.Value;

@Builder
@Value
public class ErrorResponse {

    private String code;
    private String message;

}

 

4. GlobalExceptionHandler 클래스 만들기

package com.example.exceptionhandling.exception;

import com.example.exceptionhandling.type.ErrorCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = MemberException.class)
    public ResponseEntity<ErrorResponse> handleMemberException(MemberException e){

        ErrorCode code = e.getCode();
        ErrorResponse response = ErrorResponse.builder()
                .code(code.name())
                .message(code.getMessage())
                .build();

        return new ResponseEntity(response, code.getStatus());
    }

}

>> 만약, GlobalExceptionHandler에 사용자 예외 이외의 예외처리를 한다면 아래와 같다.

더보기
package com.example.account.exception;

import com.example.account.dto.ErrorResponse;
import com.example.account.type.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import static com.example.account.type.ErrorCode.INVALID_REQUEST;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 사용자 정의 예외 사항
    @ExceptionHandler(AccountException.class)
    public ErrorResponse handleAccountException(AccountException e) {
        log.error("{} is occured.", e.getErrorCode());

        return new ErrorResponse(e.getErrorCode(), e.getErrorMessage());
    }

    // 자주 발생하는 예외 사항을 중간에 넣어준다. (자바 또는 스프링에 정의된 예외 사항들 -- 대체로 10개 가량)
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ErrorResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) {
        log.error("DataIntegrityViolationException is occured.", e);

        return new ErrorResponse(INVALID_REQUEST, INVALID_REQUEST.getDescription());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException is occured.", e);

        return new ErrorResponse(INVALID_REQUEST, INVALID_REQUEST.getDescription());
    }

    // Exception 예외 사항
    @ExceptionHandler(Exception.class)
    public ErrorResponse handleException(Exception e) {
        log.error("Exception is occured.", e);

        return new ErrorResponse(
                ErrorCode.INTERNAL_SERVER_ERROR,
                ErrorCode.INTERNAL_SERVER_ERROR.getDescription());
    }

}

 

 

[ 출처 및 참조 ]

- 부트캠프 클린코딩 예외처리 수업을 들은 후 정리

- AOP 주요 개념 : https://engkimbs.tistory.com/746

- Exception Handler 

- @Value https://mangkyu.tistory.com/167

 

|  개발의 과정에서

: 코드를 작성할 때 풀어야 하는 문제처한 상황을 함께 고려해야 한다.

ex. 어드민에 특정 기능 하나를 추가하는 것. --> 끔찍한 코드 + 하루의 기간. (if문을 하나 추가한다.). 어떤 것이 가장 효율적일까?

 

|  처한 환경이란?

: 시스템 및 비즈니스 환경 속에서 현재 내가 풀어야 하는 문제를, 어느 시점까지 처리해야하는 가를 고려해야한다.

: 빠른 시간 내에 처리를 해야할 경우, 레거시 코드가 더 효과적일 수 있다.

< 시스템 환경 >

- 저사양 임베디드 시스템 : 용량이나 RAM등 성능 자체가 이슈. 최적화가 가장 중요.
- 고성능 서버군(ex. AWS) : 성능에 대한 제한 보다는 확장성 있고 안정성 있는 시스템을 구축. 모니터링


< 비즈니스 환경 >

- 성장 중인 초기 스타트업 : 비즈니스 골든 타임을 놓치지 않도록 기능 개발을 빠르게 하는게 중요
- 안정기에 접어든 유니콘 : 사업 자체가 일정 궤도에 올라왔기 때문에 시스템을 안정화하는 게 중요

 

|  좋은 코드란?

- 일반적인 좋은 코드란?

시간복잡도/공간복잡도가 낮고,

문서화가 잘 되어 있으며,

유지보수하기 쉽고,

중복이 없으며,

가독성이 높고,

테스트하기 쉬운 코드

- 여기에 가치를 추가한 것이 좋은 코드이다.

 

|  레거시 코드란?

- 신규 프로젝트가 아닌 경우, 기존 시스템에 중간에 합류하여 기능을 추가하는 경우가 많다.

- 레거시 코드란?

더 이상 지원하지 않는 운영체제 및 다른 컴퓨터 기술에 대한 코드,
혹은 다른 사람에게 받은 소스코드이거나 이전 버전에서 받은 소스코드.

- 레거시 코드 요약 : 하휘호환성 + 내가 짜지 않은 코드

- 특징 : 다른 사람이 작성, 복잡한 이해관계, 가독성이 떨어짐

- 원인 : 단순한 기획과 정책으로 시작해 중도에 정책을 추가, 서비스 런칭.

             런칭 후 프로모션 기능 추가.

             고도화 단계에서 신규 기능을 추가하면서 이전 정책과 충돌

             -----> 예외 케이스 허용 -----> IF문의 추가 x 반복

             이후 기획자 및 개발자 로테이션 발생. 과거 히스토리를 모르는 채로 기능을 덧붙이게 된다.

- 현실적으로 중간에 개선할 시간을 주지 않는 경우가 많다.

* 레거시 코드는 어쩔 수 없는 코드. 이 코드를 개선하는 시간이 너무 오래 걸린다면 효율을 위해 코드를 건드리지 않거나, IF문을 하나 더 추가하는 것을 해주어야 한다.

 

|  왜 좋은 코드를 작성해야할까?

- 로버트 C 마틴 - 코드를 읽고 이해하는 시간이 작성하는 시간 보다 10배가 더 걸린다. 우리는 새로운 코드를 작성하기 위해 오래된 코드를 계속 읽어야 한다. 따라서 읽기 쉬울 수록 쓰기 쉬워진다. 

- 소프트웨어 Lifecycle :

: 요구사항 분석 -> 서비스 출시 -> 기능개선/정책변경/서비스확장/프로모션... 등 -> 서비스 종료

* 서비스 출시 전에는 개발단계 비용이 크지만, 출시 이후에는 유지보수 비용이 더 크다.

- 코드의 가독성을 높이는 것이 필요.

 

[ 출처 ]

부트캠프 수업 내용을 들은 후 정리

+ Recent posts