4.1 복구 가능성

복구 가능한 오류

  • 치명적이지 않음
  • 오류가 발생하더라도 사용자는 알아채지 못하도록 적절하게 처리 → 작동 계속 진행
  • 예) 사용자가 유효하지 않은 전화번호 입력 시 오류 메시지 제공하고 올바른 번호를 입력 요청
  • 다른 예) 네트워크 오류, 통계 기록하는 부분에서 오류

복구할 수 없는 오류

  • 프로그래밍 오류
  • 개발자가 어느 부분을 망쳐놓은 경우
  • 예) 잘못된 입력 인수로 호출, 일부 필요항 상태를 사전에 초기화하지 않음
  • 신속한 실패, 요란한 실패 → 피해를 최소화하고 개발자가 문제를 발견하고 해결할 가능성을 최대화 해야 함

호출하는 쪽에서만 오류 복구 가능 여부를 알 때가 많다

  • 일반적으로 코드의 잠재적 호출자에 대한 가정을 가능한 하지 않는 것이 좋음
  • 함수가 어디서 호출될지 그리고 호출 시 제공되는 값이 어디서 올지 정확한 지식이 없다

호출하는 쪽에서 복구하고자 하는 오류에 대해 인지하도록 하라

  • PhoneNumber.parse() 함수의 작성자는 이 함수에서 오류가 발생할 수 있다는 가능성을 호출하는 쪽에서 확실하게 인지하도록 해야 한다

4.2 견고성 vs 실패

신속하게 실패하라

  • 가능한 한 문제의 실제 발생 지점으로부터 가까운 곳에서 오류를 나타내는 것
  • 그렇지 않으면 문제를 추적하고 해결하기 위해 상당한 노력이 필요

요란하게 실패하라

  • 오류가 발생하는데도 불구하고 아무도 모르는 상황을 막고자 하는 것
  • 가장 명백한 방법은 예외를 발생해 프로그램이 중단되게 하는 것
  • 다른 방법은 오류 메시지 기록 (요란하지 않음)
    • 이 경우 그냥 지나갈 가능성이 있음

오류를 숨기지 않음

  • 오류를 숨기는 것은 복구할 수 없는 오류와 있는 오류 모두에 문제를 일으킴
  • 기본값 반환, 널 객체 반환, 아무것도 하지 않음 모두 오류를 숨기는 예

4.3 오류 전달 방법

  • 명시적 방법: 코드를 직접 호출한 쪽에서 오류가 발생할 수 있음을 인지할 수밖에 없도록 함
  • 암시적 방법: 코드를 호출하는 쪽에 오류를 알리지만, 호출하는 쪽에서 그 오류를 신경 쓰지 않아도 됨

예외

  • 코드에서 오류나 예외적인 상황이 발생한 경우 이를 전달하기 위한 방법으로 고안
  • 예외가 발생할 때 콜 스택을 거슬러 올라가는 데 예외를 처리하는 코드를 만나거나, 더 이상 올라갈 콜 스택이 없을 때까지 그렇게 함. 더 이상 올라갈 콜 스택이 없는 경우에는 오류 메시지를 출력하고 프로그램이 종료됨.
  • 자바는 검사 예외, 비검사 예외 개념을 모두 가지고 있음

명시적 방법: 검사 예외

  • 컴파일러가 검사 예외에 대해 호출하는 쪽에서 예외를 인지하도록 강제적으로 조치
  • 호출하는 쪽에서는 예외 처리를 위한 코드를 작성하거나 자신의 함수 시그니처에 해당 예외 발생을 선언해야 함

  • getSquareRoot() 함수를 호출하는 코드는 NegativeNumberException 예외를 처리하거나 함수 시그니처에 이 예외를 발생시킬 수 있음을 표시해야 함

암시적 방법: 비검사 예외

  • 비검사 예외를 사용하면 다른 개발자들은 코드가 이 예외를 발생시킬 수 있다는 사실을 전혀 모를 수 있음
  • 함수에서 어떤 예외를 발생시키는지 문서화하는 것이 바람직하나 개발자가 문서화하는 것을 잊어버릴 때가 있음

  • 중요한 것은 getSquareRoot()를 호출하는 함수가 예외를 확인하고 처리하지 않아도 된다는 점
  • 컴파일도 문제없음

명시적 방법: 널값이 가능한 반환 유형

  • 널 반환 → 특정값을 계산하거나 얻는 것이 불가능함을 나타내기 위한 방법
  • 널 값이 반환될 수 있다는 것을 호출하는 쪽에서 강제적으로 인지하고, 그에 따라 처리

명시적 방법: result 반환 유형

  • 널값이나 옵셔널 타입을 반환할 때 문제 중 하나는 오류 정보를 전달할 수 없다는 것
  • 언어에서 지원하지 않으면 다음과 같이 만들어서 사용해야 함

  • 다른 개발자가 result 유형을 사용하는 방법에 얼마나 익숙해지느냐가 문제
  • getValue()를 호출하기 전에 hasError() 함수를 통해 오류를 확인하지 않는다면 무용지물

  • 처리는 오류가 발생했는지 알기 위해 먼저 hasError() 호출, 오류 없으면 getValue() 호출하여 결과값 얻음
    • 오류가 발생한 경우라면 getError() 호출해서 세부정보 얻을 수 있음

명시적 방법: 아웃컴 반환 유형

  • 호출하는 쪽에서 반환값을 강제적으로 확인해야 함

  • 좀 더 복잡한 시나리오를 구현해야 한다면 더 정교한 아웃컴 유형을 사용
    • enum, 캡슐화를 위한 전체 클래스 정의
  • 호출하는 쪽에서 반환값을 무시하거나 함수가 값을 반환한다는 사실조차 인식 못할 수 있음
    • 일부 언어에서는 호출하는 쪽에서 함수의 반환값을 무시하면 컴파일러가 경고를 생성하도록 함수를 표시할 수 있음
      • 자바 CheckReturnValue 애너테이션
      • C# MustUseReturnValue 애너테이션
      • C++ [[nodiscard]]

암시적 방법: 프로미스 또는 퓨처

  • 비동기적으로 실행하는 코드

  • 오류가 발생하고 프로미스가 거부될 수 있음을 알려면 세부 구현 사항을 확인해야 함
  • 호출하는 쪽에서는 잠재적인 오류 시나리오를 완전히 알지 못함
  • 명시적으로 만드려면 result 유형의 프로미스를 반환하는 방법이 있음 (조금 복잡)

암시적 방법: 매직값 반환

  • 오류를 알리기 위해 -1 반환
  • 좋은 방법은 아님

4.4 복구할 수 없는 오류의 전달

  • 신속하고 요란하게 실패하라
  • 비검사 예외 발생
  • 프로그램이 패닉이 되도록 (언어에서 지원하는 경우)
  • 체크나 어서션 사용
  • 암시적인 기술 사용하는 게 합리적, 왜냐면 할 수 있는 게 없음

4.5 호출하는 쪽에서 복구하기를 원할 수도 있는 오류의 전달

  • 비검사 예외와 명시적 오류 전달 기법 중 어느 것을 사용해야 하는지에 대한 논쟁
  • 비검사 예외 주장
    • 코드 구조 개선: 오류 처리 한 곳에서 가능
    • 개발자들이 무엇을 할 것인지에 대해서 실용적이어야 함: 너무 많은 명시적 오류 때문에 결국 무시하게 됨
  • 명시적 기법 주장
    • 매끄러운 오류 처리: 오류가 상위로 전파된다면 사용자 친화적이지 않은 오류 메시지를 UI에 표시할 수도 있음
    • 실수로 오류를 무시할 수 없음
    • 개발자들이 무엇을 할 것이지에 대해서 실용적이여야 함: 처리해야 할 예외가 명시되어 있어서 그냥 하면 됨
  • 필자는 명시적 방식이 낫다고 봄

요약

  • 오류에는 크게 두 종류
    • 시스템이 복구할 수 있는 오류
    • 시스템이 복구할 수 없는 오류
  • 해당 코드에 의해 생성된 오류로부터 복구할 수 있는지 여부를 해당 코드를 호출하는 쪽에서만 알 수 있는 경우 많음
  • 에러가 발생하면 신속하게 실패, 에러를 복구할 수 없는 경우 요란하게 실패
  • 오류는 숨기지 말고 신호를 보내야 함
  • 오류 전달 기법은 두 가지 범주
    • 명시적 방법: 코드 계약의 명확한 부분. 호출하는 쪽에서는 오류가 발생할 수 있음을 인지한다.
    • 암시적 방법: 코드 계약의 세부 조항을 통해 오류에 대한 설명이 제공되거나 전혀 설명이 없을 수도 있다. 오류가 발생할 수 있다는 것을 호출하는 쪽에서 반드시 인지하는 것은 아니다.
  • 복구할 수 없는 오류에 대해서는 암시적 오류 전달 기법 사용
  • 잠재적으로 복구할 수 있는 오류에 대해서는
    • 명시적 기법이 좋음
  • 컴파일러 경고에 주의를 기울이라

'TIL > 좋은 코드, 나쁜 코드' 카테고리의 다른 글

3. 다른 개발자와 코드 계약  (0) 2025.03.30
2. 추상화 계층  (0) 2025.03.23
1. 코드 품질  (0) 2025.03.16

3.1 자신의 코드와 다른 개발자의 코드

  • 서로 작성한 코드가 쌓임
  • 활발하게 코드를 변경하더라도 코드의 품질이 유지되려면 코드가 튼튼하고 사용하기 쉬워야 함
  • 자신에게 분명하다고 해서 다른 사람에게도 분명한 것은 아님
    • 그들은 그 문제를 이해하고 어떻게 해결할지에 대해 생각할 수 있는 시간을 아직 충분히 갖지 못한 상태
    • 코드를 이해하기 쉽고 코드 자체로 설명이 되가 하는 것이 좋음
  • 내 코드는 독립적으로 존재하지 않는다
    • 다른 개발자가 의도치 않게 잘 실행되던 코드를 작동하지 않게 하거나 오용할 수 있음
    • 무언가 문제가 있을 때 컴파일 중지시키거나 테스트 실패하도록 만들자
  • 1년이 지난 코드를 다시 들여다보는 일은 다른 사람이 작성한 코드를 보는 것과 크게 다르지 않음
    • 배경지식이 전혀 없는 사람에게도 자신의 코드가 이해하기 쉬워야 함

3.2 여러분이 작성한 코드의 사용법을 다른 사람들은 어떻게 아는가?

  • 함수, 클래스, 열거형 등의 이름을 살펴본다
    • 편리하고 빠른 방법
    • 어떻게 사용해야 하는지에 대해 가장 잘 전달할 수 있는 방법 중 하나
  • 함수와 생성자의 매개변수 유형 또는 반환값의 유형 같은 데이터 유형을 살펴본다
    • 정적 유형의 언어에서는 올바르지 않은 경우 컴파일되지 않음
  • 함수/클래스 수준의 문서나 주석문을 읽어본다
    • 비공식적인 주석문, javaDoc같은 공식적인 코드 내 문서, 외부문서(readme 등)
    • 실제로 읽지 않음
    • 업데이트 안될 가능성 높음
  • 직접 와서 묻거나 채팅/이메일을 통해 문의한다
    • 지속가능성 없음
    • 내가 코드 기억 못할수도..
  • 여러분이 작성한 함수와 클래스의 자세한 구현 코드를 읽는다
    • 의존하고 있는 라이브러리가 많으면 적당한 크기의 기능을 구현하려 해도 수십만 줄의 코드를 읽어야 할 수도
    • 세부 코드를 읽어야만 한다면 추상화 계층의 많은 이점을 부정하는 꼴

3.3 코드 계약

  • 계약에 의한 프로그래밍
    • 어떤 코드를 호출하는 코드는 특정 요건을 충족해야 함
    • 선결조건
    • 사후조건
    • 불변사항
  • 명백한 사항과 세부 조항
    • 계약의 명확한 부분
      • 함수와 클래스 이름
      • 인자 유형
      • 반환 유형
    • 세부 조항
      • 주석문과 문서
  • 잘못된 일을 하는 것을 처음부터 불가능하게 만들어라
  • 체크나 어서션을 사용하는 방법도 있음, 그러나 아예 불가능하게 만드는 것보다는 이상적이지 않다

'TIL > 좋은 코드, 나쁜 코드' 카테고리의 다른 글

4. 오류  (0) 2025.04.13
2. 추상화 계층  (0) 2025.03.23
1. 코드 품질  (0) 2025.03.16
  • 문제를 어떻게 해결하는가도 중요하지만 문제를 해결하는 코드를 어떻게 구성하는가도 중요
  • 코드를 잘 구성한다는 것은 간결한 추상화 계층을 만드는 것으로 귀결될 때가 많다
  • 서버에서 메시지를 보내는 코드
    • 연결, 보내기, 닫기
    • 그 하위 문제들을 인식할 필요 없음 → 추상화 계층
  • 문제가 엄청나게 복잡할지라도 하위 문제들을 식별하고 올바른 추상화 계층을 만듦으로써 그 복잡한 문제를 쉽게 다룰 수 있다

 

추상화 계층 및 코드 품질의 핵심 요소

  • 가독성
    • 한 번에 한두 개 정도의 계층과 몇 개의 개념만 다루면 됨
  • 모듈화
    • 다른 계층이나 코드의 일부에 영향을 미치지 않고 계층 내에서만 구현을 변경하기가 매우 쉬워짐
  • 재사용성 및 일반화성
  • 테스트 용이성

 

API 및 구현 세부 사항

  • 코드를 호출할 때 볼 수 있는 내용
    • 퍼블릭 클래스, 인터페이스 및 함수
    • 이름, 입력 매개변수 및 반환 유형이 표현하고자 하는 개념
    • 코드 호출 시 코드를 올바르게 사용하기 위해 알아야 하는 추가 정보 (예: 호출 순서)
  • 코드를 호출할 때 볼 수 없는 내용
    • 구현 세부 사항
  • 코드를 API 관점에서 생각하면 추상화 계층을 명확하게 만드는 데 도움이 됨

 

함수

  • 각 함수에 포함된 코드가 하나의 잘 써진 짧은 문장처럼 읽히면 이상적

  • sendOwnerALetter()
    • 소유자의 주소(차량이 폐기된 경우 폐차장 주소, 차량이 아직 판매되지 않은 경우 전시장 주소, 그렇지 않으면 차량의 마지막 구매자의 주소)를 찾아 편지 한 통을 보내라
    • 여러 가지 다른 개념을 한 번에 말하고 있기 때문에 좋은 문장이 아님
  • ‘차량의 소유자의 주소를 찾아보고, 만약 발견되면, 그 주소로 편지를 보내라’
    • 차량 소유자의 주소를 찾는다
    • 주소를 찾은 경우 차량 소유자에게 편지를 보낸다
  • getOwnersAddress()
    • 같은 클래스 내에서 재사용하거나 헬퍼 클래스로 옮기고 퍼블릭 메서드를 변경
  • 코드의 가독성과 재사용성이 높아짐

 

클래스

  • 줄 수
    • 300 줄 넘지 않아야 한다? → 경험적으로는 맞지만 실제로는 사용하기에 제한적
  • 응집력
    • 순차적 응집력
      • 원두를 갈아내는 과정의 산출물은 커피를 추출하는 과정에 투입
      • 갈고 - 추출하는 것 사이에 서로 응집력이 있음
    • 기능적 응집력
      • 하나의 일을 성취
  • 관심사의 분리

  • 네 가지 핵심요소에 의해 판단
    • 가독성이 좋은가?
      • 여러 가지 다른 개념이 한 번에 구현
      • 코드 파악에 시간 오래 걸림
    • 모듈화되어 있는가?
      • 하위 문제를 해결하는 부분이 모듈화되어 있지 않음
    • 코드를 재사용하거나 일반화 할 수 있는가?
    • 코드를 제대로 테스트하기 쉬운가?

 

인터페이스

  • 하나의 추상화 계층에 대해 두 가지 이상의 다른 방식으로 구현할 경우

'TIL > 좋은 코드, 나쁜 코드' 카테고리의 다른 글

4. 오류  (0) 2025.04.13
3. 다른 개발자와 코드 계약  (0) 2025.03.30
1. 코드 품질  (0) 2025.03.16

TIL: 고품질 코드에 대하여

고품질 코드란?

  • 신뢰할 수 있고, 유지보수가 쉬우며, 버그가 거의 없는 코드

고품질 코드의 목표 4가지

  1. 작동해야 한다
    • 코드의 기본 목적은 의도한 기능을 수행하는 것
  2. 작동이 멈추면 안 된다
    • 안정적으로 동작하며 예기치 않은 실패를 최소화
  3. 변경된 요구 사항에 적응할 수 있어야 한다
    • 유연성을 갖춰 요구 사항 변화에 대응 가능
  4. 이미 존재하는 기능을 중복 구현해서는 안 된다
    • 중복 코드를 피하고 효율성을 높임

코드 품질의 6가지 핵심 요소

  1. 코드는 읽기 쉬워야 한다
    • 명확하고 직관적이어서 다른 개발자가 쉽게 이해 가능
  2. 코드는 예측 가능해야 한다
    • 동작이 일관되고 예상대로 작동
  3. 코드를 오용하기 어렵게 만들라
    • 실수를 유발할 여지를 줄이는 설계
  4. 코드를 모듈화하라
    • 기능 단위로 분리해 독립성과 유지보수성 향상
  5. 코드를 재사용 가능하고 일반화할 수 있게 작성하라
    • 반복 사용이 가능하고 특정 상황에 국한되지 않도록
  6. 테스트가 용이한 코드를 작성하고, 제대로 테스트하라
    • 테스트 가능성을 염두에 두고 철저히 검증

추가 인사이트

  • 가독성: 코드의 가독성이 떨어지면 다른 개발자가 이해하는 데 많은 시간이 소요됨
  • 모듈화: 잘 모듈화된 코드는 한 부분을 수정해도 다른 부분에 영향을 주지 않음
  • 코드 양: 코드베이스에서 코드 라인이 적을수록 좋다.
    • 우리는 코드를 많이 작성한 대가를 받는 것이 아니라, 문제를 해결한 대가를 받는다.
    • 코드란 문제 해결의 수단일 뿐이며, 적은 노력으로 문제를 해결하고 버그를 줄이는 것이 목표
  • 테스트: 코드를 작성하면서 "어떻게 테스트할 것인가?"를 계속 자문하라

고품질 코드 작성은 일정을 지연시키는가?

  • 단기적 관점: 코드 품질을 신경 쓰면 초기 작업 시간이 더 걸릴 수 있음
  • 중장기적 관점:
    • 개발 시간을 단축하고 유지보수 비용을 줄임
    • 품질을 무시하고 즉흥적으로 작성한 코드는 처음엔 빠를지 몰라도, 결국 취약하고 복잡한 코드베이스가 됨
    • 이런 코드는 이해와 추론이 어려워지고, 새 기능 추가나 버그 수정에 시간이 점점 더 많이 걸림
    • 작동하지 않는 코드를 수정하거나 재설계해야 하는 부담 발생

결론: 고품질 코드는 초기 투자가 크더라도, 장기적으로 프로젝트의 성공과 효율성을 보장한다.

'TIL > 좋은 코드, 나쁜 코드' 카테고리의 다른 글

4. 오류  (0) 2025.04.13
3. 다른 개발자와 코드 계약  (0) 2025.03.30
2. 추상화 계층  (0) 2025.03.23

 

<문제>

https://www.acmicpc.net/problem/31860

 

 

<풀이>

import sys
import heapq
N, M, K = list(map(int, sys.stdin.readline().split()))
deq = []
satisfaction = []

for _ in range(N):
    n = -int(sys.stdin.readline().strip())
    heapq.heappush(deq, n)

while -deq[0] > K:
    job = -heapq.heappop(deq)
    today_s = satisfaction[-1] // 2 + job if satisfaction else job
    satisfaction.append(today_s)
    heapq.heappush(deq, -(job - M))

print(len(satisfaction))
for s in satisfaction:
    print(s)

 

- 가장 큰 일을 구해야 하니 최대힙 사용

- 가장 큰 일을 pop하고 계산 후 다시 넣기, 반복

'TIL > [파이썬] 1일 1코테' 카테고리의 다른 글

개미_백준3048  (0) 2025.02.20
세준세비_백준1524  (0) 2025.02.19
[정렬] 좌표압축_백준18870  (0) 2025.02.18
[정렬] H-Index_프로그래머스  (0) 2025.02.17
파일 정리_백준20291  (0) 2025.02.17

<문제>

https://www.acmicpc.net/problem/3048

 

<풀이>

import sys
N1, N2 = list(map(int, sys.stdin.readline().split()))

ant1 = list(sys.stdin.readline().strip())
ant2 = list(sys.stdin.readline().strip())
T = int(sys.stdin.readline())

ant1.reverse()
totalAnt = ant1 + ant2

for _ in range(T):
    for i in range(len(totalAnt) -1):
        if totalAnt[i] in ant1 and totalAnt[i+1] in ant2:
            totalAnt[i], totalAnt[i+1] = totalAnt[i+1], totalAnt[i]
        
            if totalAnt[i+1] == ant1[-1]:
                break

print(''.join(totalAnt))

 

- ant1의 요소와 ant2의 요소가 왼쪽 오른쪽으로 정렬되어 있어야 (둘이 만나야) 순서를 바꿈

- 전체 줄에서 idx를 하나씩 올려가며 i, i+1을 비교

- 왼쪽에서 오른쪽으로 가는 ant1의 첫번째 요소가 (ant1[-1]) 순서를 바꿨을 때가 최종순서를 바꾼 형태가 되어서 break가 되어야 함

'TIL > [파이썬] 1일 1코테' 카테고리의 다른 글

열심히 일하는 중_백준31860  (0) 2025.02.23
세준세비_백준1524  (0) 2025.02.19
[정렬] 좌표압축_백준18870  (0) 2025.02.18
[정렬] H-Index_프로그래머스  (0) 2025.02.17
파일 정리_백준20291  (0) 2025.02.17

<문제>

https://www.acmicpc.net/problem/1524

 

 

<풀이>

import sys
from collections import deque
input = sys.stdin.readline

T = int(input())
games = []

for _ in range(T):
    input()
    N, M = input().split()
    
    S = deque(sorted(list(map(int, input().split()))))
    B = deque(sorted(list(map(int, input().split()))))
    temp_arr = [S, B]

    games.append(temp_arr)

for arrs in games:
    S, B = arrs

    while len(S) > 0 and len(B) > 0:
        s, b = S[0], B[0]

        if s > b:
            B.popleft()
        elif s < b:
            S.popleft()
        else:
            B.popleft()
        
    if len(S) == 0 and len(B) == 0: print('C')
    elif len(S) == 0: print('B')
    elif len(B) == 0: print('S')

 

- 차례로 비교해야 한다는 생각에 너무 어렵게 풀었음

- 작은 수가 먼저 조회되어야 하기 때문에 sort

- 시간 복잡도를 위해 deque

- 작은 것끼리 하나씩 비교해서 각각 프린트

 

 

<다른 풀이>

import sys

input = sys.stdin.readline

def solution():
    T = int(input())
    for _ in range(T):
        input()
        input()
        s = max(map(int, input().split()))
        b = max(map(int, input().split()))
        print('B' if b > s else 'S')

solution()

 

- 어차피 큰 숫자가 계속 이기게 되어있고 상대방의 그 다음 작은 수와 비교해도 마찬가지 결과

- 결국 가장 큰 수를 가지고 있는 사람이 이기는 게임

- 문제에서 친절하게 경우의 수까지 좁혀줬음 (b>s 아니면 모두 S)

'TIL > [파이썬] 1일 1코테' 카테고리의 다른 글

열심히 일하는 중_백준31860  (0) 2025.02.23
개미_백준3048  (0) 2025.02.20
[정렬] 좌표압축_백준18870  (0) 2025.02.18
[정렬] H-Index_프로그래머스  (0) 2025.02.17
파일 정리_백준20291  (0) 2025.02.17

<문제>

https://www.acmicpc.net/problem/18870

 

 

<풀이>

import sys
input = sys.stdin.readline
N = int(input())

arr = list(map(int, input().split()))
sorted_arr = sorted(set(arr))
compression_dict = {x: i for i, x in enumerate(sorted_arr)}

for i in arr:
    print(compression_dict[i], end=' ')

 

- 중복을 제거하기 위해 set

- set에서 sorted -> 배열

- 배열을 enumerate로 dictionary

- 기존 순서는 처음 배열 이용해서 순회

'TIL > [파이썬] 1일 1코테' 카테고리의 다른 글

개미_백준3048  (0) 2025.02.20
세준세비_백준1524  (0) 2025.02.19
[정렬] H-Index_프로그래머스  (0) 2025.02.17
파일 정리_백준20291  (0) 2025.02.17
회전초밥_백준28107  (0) 2025.02.15

+ Recent posts