728x90

18_Transaction_거래를_시작하지

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2024-06-29
  • 수정일: 2024-07-09

 

 

주제를 선정한 이유

최근에 Transaction 관련 문제로 코드를 한 번 크게 수정했습니다. 성능 상의 문제로 해당 Transaction은 다시 수정되었지만, 오랜만에 '이게 Transaction의 논리적 단위지🔥!'라는 깨달음을 얻어 정리하고자 합니다✍️.

 

 

Transaction

Transaction이란, 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 연산들을 의미합니다.
대표적인 예시로는 은행의 입출금이 있습니다. 내 통장에서 돈을 이체할 경우, 내 통장에서의 출금과 타인 통장에서의 입금은 각각 다른 동작이지만 반드시 한꺼번에 처리 되어야 할 논리적인 기능이 되는 것이지요.
논리적 기능을 어떻게 정의하냐는 사람마다, 상황마다 다릅니다. 예를 들어, 통장의 입출금만 하나의 트랜잭션으로 볼 것인가, 아니면 고객의 통장에 기록을 남기는 것까지 하나의 트랜잭션으로 담을 것인가 등의 다양한 경우가 만들어질 수 있습니다.

 

 

Transaction 특징(ACID)

ACID는 트랜잭션의 안전성을 보장하기 위해 필요한 주요한 특징들을 모아놓은 것입니다.

  1. 원자성(Atomicity)
    트랜잭션의 연산이 모두 데이터베이스에 반영되지 않는다면 전부 반영되서는 안된다는 원칙입니다. One or Nothing과 같은 개념이죠. 이를 통해 데이터의 오염을 방지할 수 있습니다.
    예를 들어 크레파스를 사서, 그림을 그린다가 하나의 트랜잭션이라면 크레파스만 사고 끝났다는 없습니다🫠. 크레파스를 사서 그림까지 그리던지, 그림을 안그릴거면 크레파스를 환불해야 합니다🥸.
    방금 전은 가벼운 예시지만, 원자성을 지키는 것의 중요하고도 대표적인 예시는 송금입니다. 내 통장에서 돈을 빼서 타인 통장에 입금을 할 경우, 내 통장에서 돈만 빼내는 건 안됩니다☠️. 타인 통장에 입금을 실행하지 못하게 된다면 내 통장에 돈을 다시 돌려줘야 합니다😐.

 

  1. 일관성(Consistency)
    트랜잭션 이후에도 이전과 동일하게, 즉 일관되게 데이터베이스의 제약이나 규칙을 만족시켜야 한다는 것입니다.
    송금 예시와 연결지어 생각해보면, 데이터베이스에 입금은 반드시 0원보다 커야한다라는 규칙이 있다면 제 통장에서 0원이 출금되고, 타인의 통장에 0원이 입금된다하면 원자성은 만족하지만 일관성은 만족시키지 못하게 됩니다. 트랜잭션 이후 규칙을 만족시키지 않았기 때문이죠.

 

  1. 독립성, 격리성(Isolation)
    마음에 드는 정의로 이해하시면 됩니다🫠.
  • 둘 이상의 트랜잭션이 동시에 실행될 경우, 어떤 하나의 트랜잭션이 다른 트랜잭션에 끼어들 수 없다.
  • 수행중인 트랜잭션이 완전히 완료될 때까지 다른 트랜잭션에서 수행 결과를 참조할 수 없다.
  • 트랜잭션이 동시에 실행될 때와 연속으로 실행될 때의 데이터베이스 상태가 동일해야 한다.

트랜잭션의 독립성은 여러 의미로 설명할 수 있습니다.
계속해서 송금을 예시로 설명하자면, A->B : 30만원 송금 B->C: 20만원 송금을 한다고 했을 때,
A와 B간 1개의 트랜잭션, B와 C간 1개의 트랜잭션이므로, A, B, C의 관계를 하나로 묶어서 A: 30만원 출금, B: 10만원 입금, C: 20만원 입금을 처리 할 수 없습니다.
A->B의 트랜잭션이 끝나지 않았는데, B->C 트랜잭션에서 이 결과를 참조할 수 없기 때문이죠.

 

  1. 지속성(Durability)
    성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 한다는 것입니다. 송금이 성공적으로 처리되었으면, 시스템(데이터베이스) 상에 기록이 되어야 한다는 의미입니다.

 

 

Transaction 연산

  1. Commit
    한 개의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관된 상태(=일관성)에 있을 때, 트랜잭션의 성공을, 즉 변경이 완료됬음을 트랜잭션 관리자에게 알려주는 연산입니다.
  2. Rollback
    트랜잭션이 수행되던 도중 비정상적으로 종료되어 일부만 정상적으로 처리되었을 때, 트랜잭션의 원자성을 구현하기 위해 해당 트랜잭션의 모든 연산을 취소할 것을 트랜잭션 관리자에게 알려주는 연산입니다.

 

 

정리

Transaction이란
데이터베이스에 변경을 일으키는 작업의 논리적 단위로

  1. 하나의 트랜잭션을 이루는 모든 내용은 전부 반영되거나 전부 반영되지 않아야하며,
  2. 트랜잭션 반영 이후에도 데이터베이스의 제약이나 규칙등을 지켜야 하고,
  3. 완료되지 않은 트랜잭션의 결과를 참조하면 안되며,
  4. 성공적으로 완료된 트랜잭션은 데이테베이스에 영구히 반영되어야 한다는 특징을 갖는다.

 

 

📚참고 자료

[DataBase] 트랜잭션(Transaction)이란?
[데이터베이스] 트랜잭션의 ACID 성질

728x90
728x90

17_Exception_예외입니다특별하니까요

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2024-07-01
  • 수정일: 2024-07-05

 

 

주제를 선정한 이유

프로젝트가 1차 마무리를 향해 갑니다. 새로운 구현보다는 오류 잡이를 할 시간이라는 뜻입니다. 지난 번에는 Null이었다면 이번에는 Exception입니다. 이번 글은 오류를 나타내는 값을 Null로 처리하는 것이 좋은가, Exception을 던지는 게 좋은가에 대한 고민을 정리해보고자 정리하는 글 시즌2 입니다.

 

 

Error VS Exception

Error

  • 시스템의 비정상적인 상황에서 발생
    ▶ 시스템 레벨에서 발생하기 때문에 개발자가 예측하고 처리할 수 없음

Exception

  • 개발자가 구현한 로직에서 발생
    ▶ 발생할 상황을 미리 예측하여 처리할 수 있음
    • Checked Exception
      • 컴파일 단계에서 예외를 확인하므로 반드시 예외를 처리해야 함
      • 예외 발생 시, Roll-back하지 않음
      • 예: IOException, SQLException 등
    • UnChecked Exception(Runtime Exception)
      • 실행단계에서 예외를 확인하므로 명시적인 처리를 강제하지 않음
      • 예외 발생 시, Roll-back 실행
      • 예: NullPointerException☠️, IllegalArgumentException☠️, IndexOutOfBoundException☠️

 

 

예외 처리 방법

코드에 최대한 오류가 발생하지 않게 작성한다 하더라도 예상치 못한 상황은 늘 발생합니다. 이런 상황에서 오류로 인해 프로그램이 비정상적으로 종료되지 않도록 하기 위해 예외를 처리해야 합니다.

이러한 예외 처리 방법 중 가장 대표적인 방법은 try-catch-final이 있습니다.

  • try: 예외가 발생할 여지가 있는 로직을 작성하는 부분
  • catch: 예외가 발생할 경우, 처리할 로직을 작성하는 부분
  • final: 예외 발생 유무와 상관없이 무조건 실행할 로직을 작성하는 부분
    • 주로, 커넥션 풀 종료, 임시 파일 삭제, 소켓 종료 등에 사용
try{
  // 예외가 발생할 여지가 있는 로직
} catch(Exception ex) {
  // 예외 발생 시, 처리할 로직
} finally {
  // 예외와 관계없이 수행할 로직
}

 

 

예외 처리 전략

try 구문에서 예외가 발생했다면, 그 예외를 처리하는 방법도 여러가지가 있습니다.

  1. 예외 복구
    : 예외가 발생하여도 애플리케이션은 정상적인 흐름으로 진행할 수 있도록 하는 방법
    • 예외를 잡아서 일정 시간이나 조건만큼 대기하고 재시도를 반복하고 최대 재시도 횟수를 넘어서면 예외 발생
    • 네트워크 환경이 좋지 않아 서버에 접속이 안되는 상황의 시스템에 적용하면 효율적
final int MAX_RETRY = 100;
int maxRetry = MAX_RETRY;

while(maxRetry){
  try{
    // ...
  } catch(Exception e){
    // log 출력
    // 정해진 시간만큼 대기
  } finally {
    // 리소스 반납 및 정리 작업
  }

  --maxRetry;
}

// 최대 재시도 횟수 초과 시, 예외 발생
throw new RetryFailedException();
  1. 예외처리 회피
    : 예외 발생 시, throws를 통해 호출한 메서드에 예외를 던짐
    • 호출한 쪽에서 다시 예외를 받아 처리하도록 하거나, 예외를 던지는 것이 최선일 때 사용하는 것이 좋음
public void add() throws SQLException{
  try{
    // ...
  } catch(SQLException e) {
    // 로그 출력 후, throw
    throw e;
  }
}
  1. 예외 전환
    : 예외 회피와 비슷하게 메서드 밖으로 예외를 위임하지만, 그냥 위임하지 않고 적절한 예외로 전환해서 넘기는 방법
    • 조금 더 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경해서 throws
    • 예외 처리를 상위 클래스로 단순하게 퉁치기 위해 포장(wrap)
private void add(User user) throws DuplicateUserIdException, SQLException{
  try{
    // ...
  } catch(SQLException se){
    if(se.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY){
      throw DuplicateUserIdException();
    } else {
      throw e;
    }
  }
}
public void someMethod() throws EJBException{
  try{
    // ...
  } catch(NamingException | SQLException | RemoteException e){
    throw new EJBException(e);
  }
}

 

 

Exception Handling 주의 사항

  1. catch에 아무런 로직이 없는 경우
  2. catch에 단순히 throw만 하는 경우
  3. 로그를 출력하거나, 문제를 원상 복구 시키는 로직을 첨가하는 등 catch만 수행하지 않고 해당 예외에 대한 처리를 해주어야 함

 

 

좋은 예외 처리방법

  1. null, -1, 빈 문자열 등 특수값을 예외로 사용하지 않기
    = 예외 상황은 예외 (Exception) 으로 처리하기
  2. 추적 가능한 예외 만들기
    • 오류 메세지에 어떠한 값을 사용하다가 실패하였는지
    • 실패한 작업의 이름과 실패 유형
// BAD
private double divideWrong(double a, double b){
  if(b == 0){
    return -1;
  }

  return a / b;
}

// GOOD
private double divideRight(double a, double b){
  if(b == 0){
    throw new Exception("Division by zero is not allowed");
  }

  return a / b;
}
  1. 추적 가능한 예외 만들기
    • 어떠한 값을 사용하다가 실패하였는지
    • 실패한 작업의 이름과 실패 유형
// BAD
throw new Exception("잘못된 입력입니다.");

// GOOD
throw new Exception("사용자: " + userId + "의 입력 " + inputValue + "가 잘못 되었습니다.");
  1. 의미를 담고 있는 예외 만들기
    • 예외의 원인과 내용을 정확하게 반영
// BAD
class CustomException extends Exception {}

private void connectToDatabase(){
  throw new CustomException("Connection failed because of invalid credentials.");
}

// GOOD
class InvalidCredentialsException extends Exception {}

private void connectToDatabase(){
  throw new CustomException("Connection failed because of invalid credentials.");
}
  1. Layer에 맞는 예외
    • 각 계층에서 발생할 수 있는 오류의 성격은 다르기 때문에, 해당 계층에 맞는 예외를 던지는 것이 유용
      • Repository (혹은 DAO) 에서 HttpException을 던진다거나 Presentation (Controller) 에서 SQLException을 처리하는것은 Layer별 역할에 맞지 않음
    • 가능한 가장 늦은 위치에서 처리
    • 적절한 수준으로 추상화된 Exception을 정의하거나 IllegalArgumentException 같은 Java의 표준 Exception을 활용하는 것이 좋음
// BAD
class DuplicatedException extends Exception {}
class UserAlreadyRegisteredException extends Exception {}

// GOOD
class ValidationException extends Exception {}
class DuplicatedException extends ValidationException {}
class UserAlreadyRegisteredException extends ValidationException {}
  1. 외부 SDK, 외부 API를 통해 발생하는 예외들은 하나로 묶어서 처리
    • 외부 라이브러리에서 발생하는 문제와 우리가 관리하는 코드는 같은 방식으로 해결해서는 안됨
private void billing(){
  try{
    pay.billing();
  } catch (Exception e) {
    if(e instanceof PayNetworkException){
      // ...
    } else if (e instanceof EmptyMoneyException){
      // ...
    } else if (e instanceof PayServerException){
      // ...
    }

    throw new BillingException(e);
  }
}

private void order(){
  Pay pay = new Pay();
  pay.billing();

  try{
    database.save(pay);
  } catch(Exception e){
    pay.cancel();
  }
}
  1. Catch절에 예외 흐름에 적합한 코드 구현
    • 로깅 혹은 Layer에 적합한 Exception 변환 등
      ▶ 로깅 혹은 Layer에 적합한 Exception 변환 등이 필요한 것이 아니라면 try catch로 다시 잡지 않는 것이 좋음
  2. 정상적인 흐름에서 Catch 금지 (무분별한 Catch 금지)
    • 프로그램의 정상적인 흐름을 제어하기 위해 예외를 사용하지 않음
      ▶ 예외는 오직 예외적인 경우에만 사용

 

 

📚참고 자료

[Java] 예외처리(Exception Handling) 이해하기 -1 : try - catch / throws
Java 예외(Exception) 처리에 대한 작은 생각
Exception (예외) 의 개념과 사용 이유
예외처리(exception handling)
좋은 예외(Exception) 처리
[Java] Error와 Exception
Exception Handling - 자바 예외를 처리하는 3가지 기법

728x90
728x90

16_NULL_널_생각해

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2024-06-23
  • 수정일:

 

주제를 선정한 이유

저는 작은 단위의 테스트에서는 문제 없지만, 큰 단위의 테스트를 진행할 때 제일 많이 발생했던 오류가 NullPointerException입니다. (그리고 아직도 나고 있습니다..) 그래서 null값에 대해서 if문으로 처리를 해주다 문득 null 값은 어디서 처리해주는 게 좋을까..라는 생각이 들어 정리해보고자 합니다.

 

 

원모어찬스의 널 생각해와 함께 해보겠습니다.

 

 

Null


나무위키에서 얻어왔습니다.
0이 없어요, 라면 null은 진짜 없어요인 셈이죠.

객체나 인스턴스가 생성되고 그 안의 데이터를 가져오려는데, 객체나 인스턴스가 Null, 즉 없으면 NullPointerException이 발생합니다.

객체나 인스턴스가 생성되지 않았던 것에는 여러 이유가 있습니다. 저는 특정 프로그램에 종속적인 API와 데이터베이스를 사용하고 있는데, 형변환이 안되는 객체간에 형변환하다가 NullPointerException이 발생했습니다.

 

Null 처리

아래 내용은 기억보단 기록을 - 3. 좋은 함수 만들기 - Null 을 다루는 방법에서 보고 정리하였습니다.

 

1. 초기 방어

지금 제 코드 상태도 초기 방어 형태로 되어있습니다.
간단하게 null 값 검사를 초기부터 하는 것입니다.

private void somethingGood(SomeClass imInstance){
    if(imInstance == null){
        return ;
    }

    // ...
}

제가 생각하는 장점은, 조기 리턴으로 Null 값 처리 뿐만 아니라 실행 속도 측면에서도 잘못된 경우에 다음 코드까지 실행되지 않기에 좋지 않을까 생각합니다.

 

2. Null Return X

더욱 근본으로 돌아가면 사실 null을 반환하지 않도록 하는 방법도 있습니다.

  1. null 대신 예외 던지기

위의 코드가 수정이 필요한 이유는 요즘 제 야근의 근원.. 원인을 판단하는 부분이 없습니다. return; 이나 return null;을 여러 메서드에서 사용한다면.. 어디가 문제인지 찾기 어렵습니다.. 그래서 exception을 활용해봅니다. 이 곳이 문제라는 걸 알려줘야 합니다.

 

private void somethingGood(SomeClass imInstance){
    if(imInstance == null){
        throw new MyException("내가 만든 Exception 발생!");
    }

    // ...
}
public class MyException extends RuntimeException{
    MyException(){}
    MyException(String message){
        super(message);
    }
}

MyException은 RuntimeException을 상속받았습니다. Exception을 상속받는 것과는 차이가 있는데, 다음에 자세히 정리해볼 예정입니다. 간단하게 Exception을 상속받을 경우엔 컴파일 시점에 확인되지만 RuntimeException은 컴파일 시점에 확인이 되지 않습니다. 또한, RuntimeException은 주로 논리적 오류를 처리할 때 사용되기에, Null 값 오류를 처리하기 위해서 Exception보다는 RuntimeException을 사용하는 것이 적합하다고 생각했습니다.

 

  1. Null 대신 유효한 값이 반환
    주로 예외가 발생해도 프로그램의 로직이 정상적으로 동작하기를 원하는 경우에 사용합니다. 대표적으로 자바에서는 조회 결과가 없을 때, Null보다는 빈 배열을 반환하는 경우가 있습니다. 저의 경우에는 enum을 활용했었습니다.
private ResultType somethingGood(SomeClass imInstance){
    if(imInstance == null){
        return ResultType.None;
    }

    // ...
}

이런식으로 null일 경우, 다음 로직이 이어지기 위해서 null이나 exception보다는 해당 상황에 맞는 값을 돌려주면 됩니다.

이 경우에 가장 중요한 것은

  • 오류가 발생했을 때 더 이상 진행하면 안되는 경우에는 바로 Exception을 발생시켜야 하며,
  • 유효한 값 반환으로 인해 실제 오류가 발생한 지점에서 멀리 떨어진 함수에서 오류가 발생하게 해서는 안된다는 것입니다.

 

3. 파라미터에 null 값 전달하지 않도록 조심하기

1번과 2번 사이의 타협점.. 파라미터로 넘기지 마라.. 입니다.

  • null을 return하지 마라 -> null을 인자로 전달하지 마라 -> null일 경우, 조기 return하자.

크게는 2가지 방법으로

  1. 사전에 null 값을 처리하기
if(imInstance != null){
    somethingGood(imInstance);
}

private void somethingGood(SomeClass imInstance){
    if(imInstance == null){
        throw new MyException("내가 만든 Exception 발생!");
    }

    // ...
}
  1. 파라미터에서 null 방어하기
private void somethingGood(SomeClass imInstance){
    imInstance = Objects.requireNonNull(imInstance, "Parameter 'imInstance' cannot be null");

    // ...
}
private void somethingGood(@NotNull SomeClass imInstance){
    throw new MyException("내가 만든 Exception 발생!");

    // ...
}

 

 

정리

Null 처리는

  1. 처리를 안해도 되게 Null 리턴을 좀 줄이고,
  2. 메서드의 파라미터로 Null을 전달하지 못하도록 막아도 보고,
  3. Null을 받아야 하는 경우,
    • 조기 리턴을 활용하거나
    • 예외를 발생시키거나
    • 로직 실행이 진행되어야 한다면, 유효한 값을 리턴하자.

🫠 안전한 프로그램이 되기 위해서는 이 3가지 방법을 적절히 혼용하여 사용하자!

 

 

📚참고 자료

Null(프로그래밍 언어)
3. 좋은 함수 만들기 - Null 을 다루는 방법
5. Java 자바 [예외 처리] - 사용자 정의 예외와 예외 발생 시키기, 예외 정보 얻기

728x90
728x90

15_Parameter_파최몇_파라미터_최대_몇개까지_가능

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2024-06-16
  • 수정일: 2024-06-17

 

 

주제를 선정한 이유

저는 메서드를 만들어도 수정할 일이 많습니다..🥸 생각하지 못했던 부분을 메꾸는 코드를 만들다보면.. 자꾸 매개 변수의 개수도 늘어납니다. 하지만, 기존의 코드를 하나하나 찾아가서 매개 변수가 늘었으니, 너도 인자를 하나 더 보내주렴..🫠 하는 상황에 지쳐 오랜만에 글을 적어봅니다.

 

 

주제1: Parameter를 추가하는 방법

메서드의 파라미터를 변경한다면 어떤 방식들이 있는지 정리해 보았습니다.

 

Overloading

서로 다른 매개변수를 갖는 같은 이름의 여러 개의 메서드를 정의하는 것을 오버로딩이라고 합니다.

즉, 기존의 메서드를 수정하는 것이 아니라 이름은 같지만 매개변수 타입이나 개수가 다른 메서드를 하나 더 만들어주는 것이죠.

private void helloMethod(int one){
    int i = one;
}

private void helloMethod(int one, int two){
    int i = one;
    int j = two;
}

이런 식으로 매개변수를 2개 사용하는 메서드를 추가로 만들어주게 되는 것입니다.

Default Parameter

저는 메서드 내용이 길어서, Overloading 시 코드 중복이 문제가 된다고 생각을 했습니다. 그래서 오버로딩보다는 매개변수를 추가할 때 기본값을 설정하는 방식을 사용했습니다.

private void helloMethod(int one, int two = 0){
    int i = one;
    int j = two;
}

Add Parameter

기본 파라미터 값 설정으로 해결이 안되는 경우가 있습니다. 넘어오는 인자값이 모두 유동적인 경우죠. 그럴 땐.. '그냥 기본값으로 처리하고, 메서드 내부에서 추가 처리를 해줄까..?'라는 고민을 하곤 합니다.. 하지만, 매개변수로 넘어온 값을 있는 그대로 사용하려고 해야지 추가적으로 변경해서 쓰려고하지 말라는 책의 조언에 따라.. 대대적인 메서드 수정에 나섭니다.

private void helloMethod(int one, int two){
    int i = one;
    int j = two;
}

 

 

주제2: Parameter의 타입을 결정하는 방법

계속해서 메서드의 파라미터를 변경하다보면, 그런 생각을 합니다. 그냥 데이터 객체(DTO, Model 등)를 하나 선언해서 보내는 게 나으려나..🤹 데이터 모델의 장점 중 하나가 확장성이기 때문에 메서드를 수정하지 않도고 필요한 데이터를 추가적으로 선언하면 언제든 쓸 수 있습니다. 하지만, 메서드의 파라미터에 불필요한 데이터까지 넘긴 건 아닌지 걱정이 됩니다..🚨 이것이 곧 성능 저하로 이어지지는 않을까..☠️하는 그런 문제들..

 

파라미터 3개 이상부터는 데이터 객체 사용

코드 설계 관련해서 아래 2권의 책을 읽었습니다.
(두 권 모두 추천합니다🫠!)

즉, 간단한 코드로 작성하자면 다음과 같습니다.

private void helloMethod(String userName, String userPassword, String userNickname){
    String name = userName;
    String password = userPassword;
    String nickname = userNickname;
}

private void helloMethod(UserInfo user){
    String name = user.name;
    String password = user.password;
    String nickname = user.nickname;
}

class UserInfo{
    String name;
    String password;
    String nickname;
}

여기서 추가적으로 고민해볼 수 있는 문제는

  1. 데이터 객체의 필드 중 일부만 사용될 때에도 데이터 객체를 사용하는 것이 좋은지
  2. 데이터 객체의 필드가 갖는 데이터의 크기가 클 경우에는 성능 저하의 문제가 생기지는 않는지에 대한 것입니다.

개인적으로 구글링하며 내린 결론은

  1. 파라미터로 전달하는 데이터 객체는 필요한 데이터만 들어갈 수 있도록 역할 분리를 잘 해야한다.
    저는 데이터 객체를 크게 잡아서 여기저기서 많이 쓰이면 좋지 않을까 생각했습니다. 데이터 객체가 가지는 확장성을 최대한 활용한다는 생각에서요. 그러다보니 데이터 객체가 너무 거대해지면서 1번과 같은 고민이 시작되었습니다. 데이터베이스로 비유하자면, 조인하면 성능이 떨어질 수 있으니 난 테이블을 최대한 적게 만든다하면서 테이블의 역할에 비해 저장해야 할 컬럼이 너무 많게 된 상황인 것이죠.
    그래서 결론은 목적에 맞는 데이터만 저장하는 데이터 객체를 만들자 입니다.
  2. 1번과 동일한 결론에 도달하게 되는데, 사용되지 않는 필드의 데이터가 너무 큰 값일 경우가 문제가 되는 것입니다. 만일 사용 목적에 맞는 데이터 객체를 만든다면 2번과 같은 문제도 해결할 수 있을 것입니다.

 

정리

  1. 메서드의 파라미터를 변경해야할 경우,
  • Overloading
  • Default Parameter
  • Add Parameter
  1. 파라미터가 3개 이상일 경우,
  • 데이터 객체 활용

 

📚참고 자료

[Java]메서드 오버로딩말고 파라미터를 여러개 받아보자.
읽기 좋은 코드가 좋은 코드다
내 코드가 그렇게 이상한가요?

728x90
728x90

14_NSIS_선물_대신_설치_파일

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2024-04-27
  • 수정일: 2024-05-01

 

 

주제를 선정한 이유

3월에 시작한 프로젝트가 4월 중순에 끝이났습니다. 프로그램 배포도 끝나고 수정은 간단한 UI나 기능 개선만 이뤄지고 있던 중.. 설치파일을 수정하는 일을 맡게 되었습니다. 그간 설치파일 생각없이 다음다음만 눌렀던 제 자신을 돌이켜보기 위해 글을 작성합니다☠️ 다음 번에는 좀 더 깔끔한 UI를 가진 설치파일을 만들 수 있길🔥!

 

 

NSIS 정의

NSIS (Nullsoft Scriptable Install System) is a professional open source system to create Windows installers.
즉, Windows 플랫폼에서 개발자가 인스톨러를 구축할 수 있도록 도와주는 도구입니다.

 

 

NSIS와 HM NIS Edit

NSIS를 통해 Setup 파일을 간단하게 만들 수는 있지만, HM NIS Edit을 추가적으로 사용한다면 마법사르 통해 설치 파일의 기본 틀을 보다 빠르고 쉽게 만들 수 있습니다.

 

 

WPF 애플리케이션 실행 파일 만들기

HM NIS Edit과 관련해서 설치부터 실행까지 너무나 잘 정리되어있는 글들이 많기에 저는 실제 프로젝트에서 사용한 기능을 중심으로 정리할 예정입니다.

💀 난관1: 어느 파일까지 복사할 것인가
.exe 파일을 installer로 만들려면 설치 대상에 .exe만 선택해주면 되지만, WPF 앱은 그렇지 않습니다. 제가 해봤는데, 실행이 안돼요😊..

그래서 설치 경로 먼저 수정이 필요합니다.

Section "bin" SEC01
  SetOutPath "$INSTDIR"
  SetOverwrite ifnewer
  File "..\source\WPF\SwitchThemes\SwitchThemes\bin\Debug\net8.0-windows\*"
  CreateDirectory "$SMPROGRAMS\SwitchTheme"
SectionEnd

File 부분을 기존 .exe에서 folder 전체로 변경을 합니다.

⭐ WPF는 실행을 위해 다양한 라이브러리에 대한 종속성이 필요해서 .dll이 함께 설치되어야 합니다. 또한, 리소스가 존재할 경우, 설치 파일에 함께 포함되어야 하는 경우도 있습니다.

⭐ 단, 최종 사용자에게 배포할 때는 Release 폴더로부터 필요한 파일들만 선택하여 포함시키는 것이 더 적절합니다. 그래야 필요 없는 정보를 제외해서 애플리케이션의 크기를 줄이고 정보의 노출을 막을 수 있습니다.
(실제로 회사에서도 몇 개의 파일을 제외하고 복사합니다)

💀 난관2: 체크 박스를 2개이상 넣으려면 어떻게 할 것인가..

; Finish page
!insertmacro MUI_PAGE_FINISH

이 구문과 함께라면 설치 완료 페이지를 자동을 만들어줍니다.
그러나, 문제는 해당 페이지에 체크박스를 2개 이상 포함시키고 싶을 때, 발생합니다.

우선 1개까지는 기본으로 추가하는 기능을 제공하지만, 2개부터는 커스텀이 안되고 페이지를 따로 만들어줘야 합니다.

그러나, 기본 기능을 달리 써서 어려움을 헤쳐나갈 수 있습니다.

 

간단하게 예시를 우리의 친구 StackOverFlow에서 가져와 보여드리면,

📚참고: How to set the checkbox by default checked in the windows installer Finish page using NSIS

 

How to set the checkbox by default checked in the windows installer Finish page using NSIS

I wanted to set the checkbox as checked by default in the windows installer Finish page using NSIS. For that I used the below code snipped. But it is not even displaying the checkbox in the Finish ...

stackoverflow.com

!define MUI_FINISHPAGE_RUN ""
!define MUI_FINISHPAGE_RUN_TEXT "Run Foo"
!define MUI_FINISHPAGE_RUN_FUNCTION MyRunFoo
;define MUI_FINISHPAGE_RUN_NOTCHECKED
!define MUI_FINISHPAGE_SHOWREADME "$InstDir\Bar.exe"
!define MUI_FINISHPAGE_SHOWREADME_TEXT "Run Bar"
;define MUI_FINISHPAGE_SHOWREADME_FUNCTION
!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED

MUI_FINISHPAGE_RUN과 MUI_FINISHPAGE_SHOWREADME를 활용할 수 있습니다.
이 때, README에는 설명서 대신 다른 동작을 넣는 것이죠.

Function FinishLeave 
${NSD_GetState} $mui.FinishPage.Run $0 ; or $mui.FinishPage.ShowReadme
${If} $0 <> 0 
MessageBox mb_ok "Checkbox checked"
${Else} 
MessageBox mb_ok "Checkbox not checked" 
${EndIf}
FunctionEnd

이렇게 최종 페이지 종료 후, checked 유무에 따라 로직을 추가하면 됩니다.

그러나, 만약 checkbox가 3개 이상 필요하다, 그러면 페이지를 만들어야 합니다.

!include "nsDialogs.nsh"
; nsDialogs 사용을 위한 include

Page Custom MyFinishPageCreate MyFinishPageLeave


Function MyFinishPageCreate
    nsDialogs::Create 1018
    Pop $0 

    ${If} $0 == error
        Abort
    ${EndIf}

    ${NSD_CreateCheckBox} 0 0 100% 12u "First CheckBox"
    Pop $1
    ${NSD_Check} $1

    ${NSD_CreateCheckBox} 0 15u 100% 12u "Second CheckBox"
    Pop $2
    ${NSD_Check} $2

    nsDialogs::Show
FunctionEnd

Function MyFinishPageLeave
    ${NSD_GetState} $1 $0

    ${If} $0 == ${BST_CHECKED}
        ; 로직 추가
    ${EndIf}

    ${NSD_GetState} $2 $0
    ${If} $0 == ${BST_CHECKED}
        ; 로직 추가
    ${Else}
        ; 로직 추가
    ${EndIf}
FunctionEnd

💀 난관3: 현재 프로그램 실행 여부를 어떻게 확인할 것인가..
아래 참고 자료에서 [NSIS] 프로그램 실행중일때는 확인 메시지후 종료하기를 참고하시면 됩니다.
⭐FindWindow 사용 방법을 몰랐는데, WPF 프로그램은 Class 이름을 사용할 수 없어, 윈도우 캡션을 사용한다는 것을 배웠습니다.

💀 난관4: 플러그인(DLL 파일) 추가를 어떻게 하는가..
사실 난관3을 해결하는 여러 방법 중 하나가 DLL 파일 추가가 있었는데, 알고나면 간단하지만 알기 전까지는 한참을 찾았기에 적어둡니다.

NSIS에 설치된 경로로 들어가면
C:\Program Files (x86)\NSIS\Plugins
x86-ansi 폴더와 x86-unicode 폴더가 있는데, 두 폴더에 모두 .dll 파일을 추가해줘야 합니다.
⭐ Plugins 폴더 안이 아닌 한 단계 더 들어가야 한다는 것에 주의해야 합니다.

 

 

정리

NSIS란, Window에서 Installer를 만들어주는 도구이다.
HM NIS Edit을 사용하면 마법사를 통해 기본 틀을 빠르게 만들 수 있다.
자료가 오래된 것이 많아 24년 기준 맞지 않는 내용이 있을 수 있으니, 최대한 여러 자료를 찾아보자.

 

 

📚참고 자료

NSIS Wiki
NSIS 사용자 설명서
[NSIS] HM NIS Edit로 쉽고 빠르게 설치 파일 만들기
NSIS 설치 및 HM NIS Edit 스크립트 마법사 사용하기
[NSIS] 프로그램 실행중일때는 확인 메시지후 종료하기
‘처음’ Windows 설치 파일을 ‘배포’하는 개발자들을 위하여

728x90