안녕하세요 피터입니다.

 

많은 분들이 C 언어를 배우고 나서 C++ 를 배우기 시작 할 때 어려움을 겪곤 합니다. 

제 주변에도 그런 분들이 있어서 어떤 점 때문에 어려운지 물어봤더니 어디서부터 어떻게 시작해야 될 지 모르겠다는 답변이 가장 많았습니다. 

 

어쩌면 당연한 일인지도 모르겠습니다. 

비교적 간단한 문법 체계를 갖고 있는 C 언어와 달리 C++ 은 굉장히 다양한 패러다임이 녹아있으며 방대한 스케일을 자랑하는 언어이기 때문입니다. 

 

(물론 그렇다고 해서 C 언어가 가볍고 만만한 프로그래밍 언어라는 말은 아닙니다. 오히려 그 반대로 처음엔 쉬워보이지만 알면 알수록 어려워지는 언어입니다. C 언어는 역사가 긴 만큼 괴수 분들이 많이 서식하고 계십니다 :D)

 

그래서 우선은 C 언어와 C++ 두 언어가 어떤 부분이 다른지 간단하게 살펴보고 넘어가겠습니다.

 

Feature

C

C++

Paradigm

Procedural Language

Multi-Paradigm Language

Approach

Top down

Bottom up

Namespace

X

O

Inheritance 

X

O

Overloading

X

O

Polymorphism

X

O

Template

X

O

& reference

X

O

struct constructor

X

O

Memory allocation

malloc(), calloc(), realloc()

new

Memory deallocation

free()

delete

 

여러 차이점들이 있지만 눈여겨 볼 만한 부분은 C++ 의 Multi-paradigm 이라는 부분입니다. 

 

절차지향 프로그래밍(Procedure-Oriented Programming)을 지원하는 C 언어와 달리 C++ 은 C 언어의 절차지향 프로그래밍을 그대로 승계한 동시에 객체지향 프로그래밍(Object-Oriented Programming)일반화 프로그래밍(Generic Programming)을 모두 지원하는 언어입니다. 

 

C 언어의 문법을 그대로 계승했기 때문에 얼핏 보면 큰 차이가 없어 보여 쉽게 접근했는데,

코딩을 하면 할 수록 내가 알던 그 언어와 비슷하면서도 뭔가 잘못되고 있다는 느낌을 받게 되죠. 

(내거인듯 내거아닌 내거같은 너...)

 

그렇게 느낄 수 밖에 없는 것이 객체지향 프로그래밍에 대한 개념을 모르는 상태에서는 C++에서 새롭게 등장하는 객체지향 문법들을 온전히 이해 할 수 없기 때문입니다. 

 

게다가 처음보는 템플릿(template) 문법은 외계어에 가깝게 느껴집니다.

 

C++은 이처럼 여러 가지 패러다임을 지원하는 언어이니 만큼 제대로 사용하기 위해서 배워야 할 개념들이 많습니다.

그래서 배우면 배울 수록 겸손해지는 언어이기도 하죠. 

(괜히 개발자들 사이에서 본인이 C++ 고수라고 하는 사람은 상종도 하지 말라는 말이 있는 게 아닙니다...)

 

 

그렇다면 C++을 어떻게 배우는 것이 좋을까요?

우선은 기존의 Top-down 방식으로 사고를 했던 것에서 벗어나 Bottom-up 방식의 객체지향 개념을 먼저 익히는 것이 좋습니다. 

 

[객체지향] Object-Oriented Programming 핵심 개념의 이해

배경 데이터 흐름(Flow)에 기반한 절차지향적 프로그래밍 방법은 복잡한 로직을 갖는 큰 규모의 소프트웨어 개발에는 적합하지 않습니다. 하드웨어 성능이 폭발적으로 성장하면서 요구되어지는

gracefulprograming.tistory.com

 

객체지향 개념을 이해하고 나서 문법적인 면에서는 클래스(class)접근 제한자(access modifier), new, delete 등의 문법을 숙지하셔야 합니다. 

 

기존 C언어에서 malloc(), free() 대신 new, delete 를 사용하면 생성자(constructor)/소멸자(destructor)가 호출되는데 이러한 개념도 익혀두셔야 합니다. 

 

간단한 프로그램을 객체지향적으로 설계하고 구현하는 것을 반복적으로 하다 보면 이제 Bottom-up 방식으로 사고 하는 것이 어느정도 익숙해지게 됩니다. 

 

 

이제 슬슬 템플릿(template)을 배워보도록 합니다.

템플릿은 일반화 프로그래밍(generic programming) 패러다임으로 데이터 처리하는 로직을 데이터 타입에 비종속적으로 구현할 수 있게 해줍니다. 

(컴파일 타임에 주어진 타입 별로 클래스들이 분화됩니다)

 

템플릿 문법을 직접 사용해보는 것도 좋지만 STL(Strandard Template Library)을 잘 다루는 것이 매우 중요하기 때문에 STL의 컨테이너(container)들을 활용하는 연습을 많이 하는 것이 좋습니다.  

 

vector, list, map 등의 주요 컨테이너들을 능숙하게 다룰 수 있게 되면 특별한 경우를 제외하면 대부분의 로직을 구현할 수 있습니다. 

 

 

 

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

-Peter의 우아한 프로그래밍

 

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요


개요

 

nohup 명령어는 리눅스에서 프로세스를 실행한 터미널의 세션 연결이 끊어지더라도 지속적으로 동작 할 수 있게 해주는 명령어입니다.


기본적으로 터미널에서 세션 로그아웃(logout)이 발생하면

리눅스는 해당 터미널에서 실행한 프로세스들에게 HUP signal 이 전달하여 종료시키게 되는데,

이 HUP signal을 프로세스가 무시(ignore)하도록 하는 명령어라서 nohup 이라는 이름인 것입니다. 

 

그래서 결과적으로 터미널에서 연결이 끊기거나 터미널을 종료해도 실행했던 프로세스들이 계속 실행될 수 있는 것입니다.

 

또한 nohup 명령어는 표준 출력(standard output)nohup.out 파일로 재지향(redirection) 합니다.

 

터미널이 종료되어도 표준 출력은 nohup.out 파일에 계속해서 기록되기 때문에 프로세스의 상태를 확인하는데 유용 할 수 있습니다.

그러나 필요 이상의 로그를 화면에 계속해서 출력하게 되면 nohup.out 파일의 용량이 매우 커지기 때문에  디스크 공간을 낭비하게 될 수 있습니다.

 

따라서 꼭 필요한 로그만 출력하거나 로그를 남기는 것이 불필요한 경우 재지향을 통해 nohup.out 파일을 생성하지 않도록 하는 것이 좋습니다. 

 

 

 

사용법

 

nohup 명령어의 사용법은 매우 단순합니다. 

 

nohup [프로세스] &

 

[프로세스] 부분에 실행하고자 하는 프로그램이나 스크립트를 지정하면 됩니다.

여러분이 작성한 스크립트를 넣어도 되는데 스크립트 파일의 권한이 755 이상이어야 합니다.

 

일반적으로 nohup 명령어를 사용할 때는 백그라운드 작업으로 실행하는 경우가 많기 때문에 뒤에 & 를 붙여서 백그라운드 실행이라는 것을 명시해 줍니다.

 

nohup.out 파일을 생성하지 않으려면 표준출력과 표준에러를 /dev/null 로 재지향 해주면 됩니다. 

nohup [프로세스] 1>/dev/null 2>&1 &

 

1>/dev/null 은 표준 출력을 사용하지 않겠다는 의미이고, 2>&1 은 표준 에러를 표준 출력과 같게 만드는 명령어입니다. 

 

nohup 으로 실행한 프로세스를 종료하려면 먼저 ps 명령어로 PID를 식별한 다음,

kill 명령어를 사용하여 해당 프로세스에 종료 시그널을 보내서 종료해야 합니다. 

 

ps -ef | grep [프로세스명]

# PID 식별 후
kill -15 [PID]

# 종료되지 않으면 강제 종료 (비권장)
kill -9 [PID]

 

 

위에서 사용된 재지향, kill, ps 명령어 등은 아래 글들을 참고해주세요.

 

[Linux] 재지향(Redirection)에 대한 이해

안녕하세요 피터입니다. 오늘은 리눅스의 I/O 재지향(redirection)에 대해서 설명드리겠습니다. 재지향은 파이프(pipe)와 더불어 리눅스의 명령어들을 훨씬 강력하게 무장시켜주는 역할을 하는 핵심

gracefulprograming.tistory.com

 

[Linux] kill 프로세스를 '안전하게' 종료시켜보자

개요 kill 명령어는 이름 때문에 프로세스를 강제로 종료시키는 명령어로 오해를 사기 쉬운데 실제로는 프로세스에 시그널(signal)을 보내는 명령어입니다. 이름이 kill 인 이유는 어떤 시그널을 보

gracefulprograming.tistory.com

 

 

[Linux] ps 로 실행 중인 프로세스 확인하기

개요 ps 명령어는 리눅스에서 현재 실행중인 프로세스를 확인하는 명령어 입니다. Process Status에서 따온 이름이죠. 이름 그대로 명령어를 실행하면 현재 실행되고 있는 프로세스들의 정보를 화면

gracefulprograming.tistory.com

 

 

 

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

 

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

개요

 

안녕하세요 피터입니다.    

오늘은 Bash 쉘 스크립트에서 사용되는 변수(variable)에 대해서 설명드리겠습니다. 

변수는 모든 프로그래밍 언어와 스크립트 언어에서 핵심적인 기능을 합니다. 데이터를 조작하기 위한 연산이나 문자열을 파싱(parsing)하는데 있어서 없어서는 안될 꼭 필요한 존재입니다.

 

변수는 단지 데이터를 담고 있는 메모리상의 위치를 나타내는데 어떤 변수에 어떤 데이터가 담겨있는지 쉽게 알 수 있게 이름을 붙여놓은 것입니다. 

 

 

변수 할당(assignment)

 

데이터를 담는 공간인 변수를 사용하려면 먼저 변수를 할당(assignment) 해야 합니다.

 

변수는 할당 연산자 = 를 사용해서 할당합니다.

 

a=100
b="hello"

 

주의 해야 할 것은 할당 연산자 앞뒤로 공백이 없어야 한다는 것입니다.  

(또한 테스트 연산자도 = 심볼을 쓰기 때문에 헷갈리지 말아야 합니다. 상황에 따라 해석이 달라지니 주의해주세요. 이 부분은 나중에 테스트 연산자 설명드릴 때 다시 말씀드리겠습니다.)

 

Bash 는 다른 프로그래밍 언어들과는 달리, 변수를 "타입"으로 구분하지 않습니다.

Bash 변수는 본질적으로 문자열이지만 Bash 가 문맥에 따라서 정수 연산이나 변수를 비교해 줍니다. 이 동작을 결정짓는 요소는 그 변수값이 숫자로만 되어 있는냐 아니냐 입니다.

 

 

let 명령어로 할당 할 수도 있습니다.

let은 bash 의 내장 명령어 중에 하나로 간단한 산술식을 수행 할 수 있습니다.

 

let a=1000+4
let "a = 1000 + 4"   # 위와 같음

 

가독성을 위해서는 " " 를 이용해서 쿼우팅(quoting) 하고 연산자 사이에 빈칸(space)을 넣어주는게 좋습니다. 

 

 

변수를 할당 할 때는 위의 숫자 100이나 문자열 "hello"와 같은 리터럴 상수(literal constant) 뿐만 아니라 명령어의 결과를 할당 할 수도 있습니다. 

 

바로 명령어 치환(command substitution)을 사용하는 방법인데요.  역따옴표(` `) - 백틱(backticks)으로 명령어를 감싸면 해당 명령어의 결과로 치환되어 그 결과를 변수에 바로 할당 할 수가 있습니다. 

 

ret=`ls -l`

 

명령어 치환에 대해서는 다룰 내용이 좀 더 많기 때문에 나중에 포스팅을 추가로 하겠습니다. 

 

 

 

변수 치환(substitution)

 

변수는 데이터를 담고 있는 그릇 역할을 하므로, 할당하였으면 이제 변수에 담겨있는 데이터를 사용 할 수 있어야 합니다. 

변수에는 어떤 계산을 하기 위해 임시로 저장한 중간값이나 특정 명령어의 결과값 등이 저장되어 있는데 이 값들을 확인하거나 적절히 활용하기 위해서는 치환(substitution)을 통해 변수의 데이터를 참조해야 합니다.

 

치환은 ${variable} 구문을 이용하여 사용하는데 $variable 로 줄여 쓸 수 있습니다. 

 

참조되는 값을 큰따옴표(" ")로 묶어도 변수 치환이 일어나는 것을 막지 못합니다.

이를 부분적 쿼우팅(partial quoting)이나 약한 쿼우팅(weak quoting)이라고 합니다. 

 

작은따옴표를 쓰게 되면 변수 이름이 그냥 문자 그대로 해석되어 아무런 일도 일어나지 않습니다.

이를 완전한 쿼우팅(full quoting)이나 강한 쿼우팅(strong quoting)이라고 합니다.

 

#!/bin/bash

a=1004
echo "$a"  # $a 변수 치환이 일어남 
echo '$a'  # $a를 문자 그대로 해석 


실행 결과
100
$a

 

 

변수를 할당하지 않고 참조하게 되면 문제가 생깁니다.

변수에 어떤 값을 처음 할당 하는 것을 초기화(initialize)라고 하는데, 초기화가 안 된 변수는 "null" 값을 가집니다.

이는 값이 할당 안 된 것이지 0 이라는 값을 갖는다는 이야기가 아닙니다. 

 

변수를 참조하기 전에 반드시 할당을 먼저 해주시기 바랍니다. 

 

 

 

 

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 남겨주시면 감사하겠습니다.

 

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

 

배경

 

데이터 흐름(Flow)에 기반한 절차지향적 프로그래밍 방법은 복잡한 로직을 갖는 큰 규모의 소프트웨어 개발에는 적합하지 않습니다.

 

하드웨어 성능이 폭발적으로 성장하면서 요구되어지는 소프트웨어는 점점 복잡해지고 거대해졌는데

기존의 전통적인 절차지향 개발 방법 으로는 소프트웨어를 설계 및 구현하는데 많은 어려움이 생긴 것이죠.

(사실 소프트웨어 공학 - software engineering 이라는 개념도 생소할 시기였습니다)

 

이러한 문제를 해결하기 위해 프로그램을 함수(procedure) 단위로 나누어 구조화하는 구조적 프로그래밍 방법이 대두되었는데, 

상위로부터 하위로 쪼개나가는 방식이기 때문에 Top-Down 방식이라고도 합니다. 

하지만 함수는 데이터를 처리하는 부분은 구조화할 수 있었지만 데이터는 구조화하지 못해  전역 네임스페이스 포화 문제를 유발하게 되었습니다. 

 

출처: IT World

 

 

 

또한 데이터의 상태에 따라 다르게 동작하는 함수들이 많아지면서 각 함수들로 전달되는 변수들이 적절한 값을 갖는지 확인하는 코드가 늘어났으며,

문제가 발생했을 때 함수에 영향을 주는 변수를 추적하는 것이 프로그램의 규모가 커질 수록 어려워졌습니다.

프로그램에 버그가 있는 경우 원인을 분석하는데 그만큼 많은 시간과 노력이 더 필요해지는 것이죠.

 

즉, 소프트웨어의 유지 관리 비용이 규모에 따라 대폭 증가하는 문제가 발생합니다. 

이것이 하드웨어가 무어의 법칙(Moore's law)대로 폭발적으로 성장 하던 시기에 소프트웨어의 위기(software crisis)를 초래한 가장 큰 이유 중에 하나입니다. 

 

 

이러한 문제들을 해결하기 위해 등장한 것이 객체지향 프로그래밍(Object-Oriented Programming) 입니다. 

(줄여서 OOP 라고도 많이 불립니다.)

 

객체지향 프로그래밍은 큰 문제를 작게 쪼개는 방식이 아니라 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 이 객체들을 조합해서 큰 문제를 해결하는 Bottom-Up 방식을 지향합니다.

 

각각의 객체들은 다른 객체들과 독립적으로 운용이 가능하며 객체 외부에서 접근이 가능한 인터페이스를 제한하여 잘못 사용되어지는 경우를 최소화 할 수 있습니다. 

 

독립적이라는 것은 다른 객체와의 의존성(dependency)이 낮다는 의미인데,

하나의 객체의 코드를 수정했을 때 해당 객체를 사용하는 객체는 코드를 수정하지 않아도 되면 의존성이 낮은 약한 결합 관계(weak coupling)이고, 반대로 다른 객체의 코드도 수정해야 되는 경우는 의존성이 높은 강한 결합 관계(strong coupling)라고 할 수 있습니다. 

 

객체간 독립성이 높계 설계되어 있다면 요구사항 변경 등의 이유로 코드 수정이 필요할 때 상대적으로 적은 범위의 코드만 수정해도 되기 때문에 유지 관리 비용을 낮출 수 있습니다. 

 

또한 외부에서 사용하는 인터페이스 외에는 객체 내부로 숨겨(information hiding) 객체의 기능(method)이나 속성(property)이 설계와 다르게 동작할 위험을 최소화 할 수 있기 때문에 신뢰성이 높습니다. 

 

객체들의 독립성/신뢰성을 높게 설계 해 놓으면 별다른 수정 없이 재사용 할 수 있고,

요구사항이 변경되어 코드를 수정하게 되도 제한된 영역만 수정하면 되기 때문에 개발 기간과 유지관리 비용을 비약적으로 줄일 수 있게 되었습니다. 

 

 

객체지향 핵심 개념

 

객체지향(Object-Oriented)이라고 하면 객체를 지향한다는 말이 선뜻 이해가 되지 않을 수 있습니다. 

원문의 oriented를 지향(志向)이라고 바꾸지 않고 그대로의 의미를 한번 보겠습니다.

 

 

-Oriented 를 [~에 주된 관심을 두다], [~를 주된 관심으로 삼다] 정도로 해석하면 좀 더 이해가 쉽습니다. 

즉, 객체지향 프로그래밍은 객체(object)를 주된 관심으로 삼는 프로그래밍 방법론을 뜻하는 것입니다.

 

그렇다면 객체는 무엇을 말하는 걸까요?

여기에서 말하는 객체를 이해하려면 먼저 객체 지향의 핵심 개념 중에 하나인 추상화라는 개념을 이해해야 합니다.

 

 

 

추상화 (abstraction)

 

추상화(抽象化 : abstraction)란 현실 세계에서 특정한 대상을 관찰하여 핵심적이고 특징적인 공통점들을 뽑아내는 과정을 말합니다.

 

피카소는 추상화(抽象畫 : abstract painting)의 대가입니다. 피카소의 그림들은 얼핏 단조로워 보일 수 있지만 핵심적인 특징들이 매우 잘 나타나 있습니다. 

이것은 그가 그림을 그리기 전에 오랜 기간 대상을 관찰하였기 때문에 가능했던 것이죠.

 

아래의 그림은 오랜 기간 황소를 관찰하고 그림을 그리면서 핵심적인 특징만 남겨두고 제거해나간 과정입니다.

마지막 황소 그림을 보면 단순한 선 몇 개로 황소를 표현하였지만 특징이 잘 나타나 있어 한눈에 봐도 황소라는 것을 알 수 있습니다. 

 

황소 (피카소), 1945

 

추상화(abstraction)에서 핵심적인 특징이라는 것은 대상에 대한 현재 내가 구현하고자 하는 프로그램에서의 관심사라고 할 수 있습니다.  

그 외의 특징들은 어떤 관점에서는 중요한 특징들일지 몰라도 현재 프로그램을 구현하는데 불필요하기 때문입니다.

 

이렇게 줄이고 줄여서 뽑아낸 공통적인 특징들은 크게 속성(attribute)행위(behavior)로 나뉘게 되는데 이것들을 관련있는 것들끼리 묶어놓은 것을 캡슐화(encapsulation)라고 합니다. 

그리고 관련있는 것들을 하나로 묶는 개념적(추상적)인 주체가 바로 클래스(class)입니다.

 

attribute = property = member variable = field = state
behavior = method = member function = operation

 

객체(object)는 클래스로부터 실체화된 것을 말합니다.

클래스는 추상적이기 때문에 눈에 보이지 않고 개념적으로만 존재하지만 객체는 눈에 보이는 실체가 있습니다. 

 

예를 들어 "바나나"라는 클래스가 있다고 하면 우리는 개념적으로 바나나가 갖고 있는 특징들을 떠올릴 수 있습니다. 껍질은 노란색이고 속은 하얗고 맛은 달고 길쭉한 모양이라는 특징들을요. 

하지만 실제로 바나나를 먹기 위해서는 마트에 가서 진열대에 놓여있는 실제의 "바나나(object)" 사와야 합니다.  

마트 직원에게 "바나나 어디 있나요?" 라고 물어볼 때 마트 직원이 안내를 해 줄 수 있는 것은 나와 마트 직원이 모두 바나나라는 추상적인 개념을 알고 있기 때문에 가능한 것입니다. 

진열대에는 바나나가 여러 개 있을 수 있죠. 그 중에 내가 어떤 바나나를 사서 먹었다면 그 바나나는 다른 바나나들 구별되는 유일한 실체입니다.

 

위 예문에서 글씨색으로 클래스와 객체를 구분해놓았는데 차이를 아시겠지요?

개(dog)라는 클래스가 있다면 우리집 강아지 로이, 옆집 강아지 초코는 객체인 것이죠.

 

 

 

캡슐화(encapsulation)

 

위에서 설명드린대로 캡슐화란 속성과 행위들을 관련있는 것끼리 묶는 것입니다. 

이 때 중요한 것은 외부에서 접근이 필요한 부분을 제외하고는 내부로 숨기는 것입니다. 

 

우리가 알약을 먹을 때 중요한 것은 약의 성분이 인체에 미치는 영향이지 약의 맛이나 식감이 아니기 때문에 캡슐 속으로 숨기는 것과 마찬가지이죠.

 

이렇게 외부로부터 세부적인 내용을 숨기는 것을 정보 은닉(information hiding)이라고 합니다. 

세부 구현을 숨기는 목적은 클래스 내부 구현의 응집도(cohesion)를 높이고 외부 다른 클래스와의 결합도(coupling)을 낮추는 데 있습니다. 

 

일반적인 객체지향 언어에서는 이 때문에 접근제한자(access modifier)라는 문법을 지원합니다. 

  • public : 클래스 외부에서 제한 없이 접근 가능 (nobody)

  • private : 클래스 외부에서 접근 불가 (클래스 내부에서만 접근 가능)

  • protected : 상속한 하위 클래스에서만 접근 가능

 

 

 

상속(inheritance)

 

상속은 객체지향의 핵심 기능 중에 하나로 대상이 되는 클래스의 모든 특징들을 물려 받는 것을 말합니다. 

 

어떤 임의의 클래스 B가 다른 클래스 A를 상속 받게 되면 A 클래스의 속성과 행위들을 모두 물려받게 됩니다. 

이 때 클래스 A와 B는 상속 관계(inheritance relation)에 있다고 하며 클래스 A부모 클래스(parent class), 클래스 B를 자식 클래스(child class)라고 부릅니다. 

 

Vehicle hirerachi

 

어떤 클래스를 이미 상속 받은 클래스를 다시 다른 클래스가 상속을 받을 수 있습니다. 

WheeledVehicle 클래스는 Vehicle 클래스의 Child 클래스인 동시에 Bicycle이나 Car 클래스의 Parent 클래스가 되는 것이죠.

 

그렇게 되면 클래스 간의 관계가 위와 같은 계층형 구조(hierachical structure)를 형성하게 되는데요,

이런 형태의 관계에서는 아래로 내려갈수록 구체화(specialize)된다고 하고 위로 갈수록 일반화(generalize)된다고 표현합니다. 

 

클래스가 구체화 될수록 고유의 특징들이 더 많이 생겨나게 되고, 일반화 될수록 더 많은 객체에 영향을 주게 됩니다. 

 

부모 클래스로부터 특징들을 물려받게 되면 이미 구현된 세부 내용을 다시 구현할 필요가 없기 때문에 코드의 재사용성(resusability)이 향상됩니다. 

많은 분들이 클래스 간의 상속 관계를 형성 할 때 이처럼 코드의 재사용성에 주안점을 두게 되는데 자칫하면 더 큰 혼란을 야기할 수 있어 주의가 필요합니다. 

 

상속 관계가 성립하려면 두 클래스 간의 관계가 is-a 관계여야 합니다.  is-a 관계란 [~은 ~이다] 라고 부를 수 있는 관계입니다. (예를들면 바나나과일이다)

즉, 자식 클래스가 들어간 문장이 있을 때 자식 클래스를 부모 클래스로 대체하여도 의미가 성립되어야 합니다. 

 

위 그림에 있는 WheeledVehicle 은 "바퀴가 있는 탈 것"이라는 뜻입니다. 

  • 자동차(Car)는 WheeledVehicle 이다.

  • 자전거(Bicycle)은 WheeledVehicle 이다. 

  • 보트(Boat)는 Vehicle 이다. 

세 문장 모두 is-a 관계에 있기 때문에 제대로 된 상속 관계라고 볼 수 있습니다. 

 

일반적으로 가장 많이 하는 실수는 has-a 관계인 클래스를 상속하는 경우입니다.

has-a 관계는 사람-팔, 자동차-바퀴, 새-날개 등을 예로 들 수 있습니다. 

즉, 하나의 클래스가 다른 클래스의 일부로 속할 때 has-a 관계가 성립됩니다.

 

이러한 has-a 관계의 클래스를 코드 재사용성 측면만 고려하여 상속을 하게 되면 자연스러운 모델링이 되지 못하고 큰 혼란을 불러 올 수 있습니다. 

 

그렇다면 상속은 어떠한 경우에 사용하는 것이 좋을까요?

바로 다음에 설명드릴 다형성을 구현하기 위해서 사용하는 것을 권장드립니다.

물론 기본적으로 is-a 관계가 성립되어야 겠죠?

 

 

 

다형성(polymorphism)

 

다형성은 그리스어에서 기원된 단어의 합성어로 '여러 형태가 존재한다' 라는 뜻입니다.

객체지향에서 다형성이란 하나의 속성이나 행위가 상황에 따라 다른 의미로 해석될 수 있는 특징을 말합니다.

 

다형성이야말로 객체지향의 꽃이라고 할 수 있습니다. 

그만큼 중요하고 핵심이 되는 개념 중에 하나입니다.

 

다향성을 구현하는 방법 중에 대표적인 방법이 바로 위에서 설명드린 상속(inheritance)을 이용한 방법입니다.

상위 클래스의 메소드를 하위 클래스에서 재정의(override)하여 상위 클래스의 참조변수가 어떠한 하위 클래스의 인스턴스참조하느냐에 따라 동작이 달라지는 개념으로 이러한 방식으로 구현되는 다형성을 서브타입 다형성(subtype polymorphism) 이라고 합니다. 

 

예를들어 이동(move)라는 행위는 여러 가지 형태를 가질 수 있습니다. 

"현재 위치에서 특정 위치로 옮겨간다" 라는 목적은 같지만 실제로 이동하는 대상에 따라서 이동하는 방식이 달라질 수 있는 것이죠.

직립보행을 하는 사람은 두 발로 걷거나 뛸 수 있으며 자동차나 자전거 등의 이동 수단을 사용 할 수도 있습니다. 

개나 고양이 같은 동물들은 네 발로 이동을 하고 새나 날개가 있는 곤충들은 날아서 이동 할 수도 있습니다.

 

이처럼 이동(move)이라는 행위에 대해 여러 가지 형태가 존재하기 때문에 전통적인 프로그래밍 방법으로는 if 구문이 반복적으로 사용되는 복잡한 코드가 생겨날 가능성이 높습니다.

 

 

객체지향에서는 상속(inheritance)재정의(override)를 통해 효과적으로 구현해낼 수 있습니다. 

 

클래스간의 관계가 위와 같고 하위 클래스에서 Move() 메소드를 재정의 하고 있다고 전제하면 아래와 같이 Animal 클래스 타입의 참조로 다형성을 구현해 낼 수 있습니다. 

List<Animal> animals = new List<Animal>();

animals.add(new Human());
animals.add(new Dog());
animals.add(new Bird());

foreach(Animal animal in animals)
{
	animal.Move();
}

 

위 코드는 이해를 돕기 위해 C#으로 간단하게 작성한 코드입니다. 실제 구현은 세부적인 내용이 더 늘어나겠지만 전체적인 맥락은 크게 변하지 않습니다. 

 

animals List에 들어 있는 객체(instance)들은 각각 Human, Dog, Bird 타입의 인스턴스지만 Animal 타입으로 참조하고 있습니다. 

Animal 타입으로 일반화 하여 Move() 메소드를 호출하면 Animal 클래스에 정의되어 있는 Move()가 실행되는 것이 아니라 각각의 하위 클래스에 구현된 Move() 메소드가 호출됩니다. 

 

이렇게 비즈니스 로직(bussiness logic)을 일반화된 코드로 구현해놓으면 여러 가지 장점이 생깁니다. 

요구사항(requirements)이 변경되어 코드를 수정해야 하는 경우에 위처럼 일반화된 코드는 변경하지 않고 기존 클래스의 Move() 메소드만 수정하거나 클래스를 추가 구현하는 것으로 해결할 수 있습니다. 

 

또한 코드의 가독성(readability)이 높아지고 일관성이 생기기 때문에 개발자의 실수를 줄일 수 있게 됩니다. 

 

 

정리

 

지금까지 객체지향 개념이 만들어지게 된 배경과 핵심 개념에 대해서 설명드렸습니다. 

객체지향 프로그래밍을 한다는 것은 단순히 객체지향 언어를 사용하여 개발을 하는 것을 뜻하지는 않습니다. 

 

많은 분들이 객체지향을 공부 할 때 객체지향을 지원하는 언어(C#이나 Java 등)의 도서를 구매하여 문법이나 언어적인 특성을 익히는 것으로 시작하는데 문법 보다는 개념원리를 이해는 것이 우선입니다. 

 

그래야만 객체지향적으로 사고 할 수 있으며 보다 유연하고 안정적인 프로그램을 설계 할 수 있는 능력을 배양 할 수 있습니다. 

 

이 글을 읽으신 분들께 객체지향을 이해하는 데 도움이 되었길 바라며 앞으로 객체지향 원칙, UML, 디자인 패턴(design patterns) 등의 글들을 써나갈 계획입니다. 

 

 

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

 

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

 

개요

 

kill 명령어는 이름 때문에 프로세스를 강제로 종료시키는 명령어로 오해를 사기 쉬운데 실제로는 프로세스에 시그널(signal)을 보내는 명령어입니다. 

이름이 kill 인 이유는 어떤 시그널을 보낼 지 지정하지 않으면 기본적으로 SIGTERM 시그널을 보내게 되는데 SIGTERM의 기본 동작이 프로그램 종료이기 때문입니다. 

 

물론 프로세스에 SIGKILL 시그널을 보내 강제로 종료시킬 수도 있습니다. 

그렇지만 일반적인 상황에서 SIGKILL 시그널을 보내는 것은 권장되지 않습니다. 

 

본래 프로그램을 설계 할 때 대부분의 경우 종료 시그널을 받았을 때 처리하고 있던 데이터가 안전하게 정리 될 수 있도록 설계합니다. 

SIGTERM, SIGINT 등의 종료 시그널에 대해 시그널 핸들러(handler)를 등록하여 시그널이 수신되었을 때 연결된 Socket 또는 File을 close 하거나 Queue에 적재된 데이터를 File로 Dump하기도 합니다. 

처리 중이던 데이터를 안전하게 정리/보관하여 데이터 유실을 방지하고 다시 실행하였을 때 기존 작업을 이어나갈 수 있게 하기 위함입니다. 

그런데 SIGKILL 시그널은 프로그램에서 핸들러를 만들 수 없는 시그널이기 때문에 위와 같은 처리들을 구현 할 수 없습니다. 따라서 SIGKILL 시그널을 통해 프로세스를 강제로 종료해버리면 데이터 유실과 같은 문제가 생길 우려가 있습니다. 

 

kill -KILL PID

or

kill -9 PID

프로세스를 안전하게 종료시키려면 위와 같이 SIGKILL을 통한 종료는 가급적 사용하지 않는게 좋습니다. 

 

 

시그널(signal)

 

[root@peterdev dev]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

kill 명령어에 -l 옵션을 주면 위와 같이 시그널의 숫자(number)와 이름이 출력됩니다. 

 

시그널을 보낼 때는 kill -(보낼 시그널) PID 이렇게 사용하는데, 지정된 PID를 갖는 프로세스에 지정된 시그널이 전달되는 구조입니다. 

 

시그널은 숫자로 지정해도 되고 앞에 SIG를 빼고 이름을 넣어도 됩니다. 

kill -INT PID

or

kill -2 PID

 

위에서 언급한 대로 아무런 시그널을 지정하지 않고 kill PID 이라고만 하면 SIGTERM 이 전달됩니다. 

 

 

특정 이름의 프로세스 모두 종료하기

 

kill `ps -ef | grep 프로세스이름 | grep -v grep | awk '{print $2}'`

 

파이프grep, awk, 역따옴표(backticks : 백틱)을 조합하면 특정 이름의 프로세스를 모두 찾아서 종료 시킬 수 있습니다. 

 

파이프와 grep은 리눅스에서 가장 많이 사용되는 명령어 조합 중에 하나로 이전 스트림의 출력값에 원하는 형식으로 필터링을 할 수 있습니다. 

위 스크립트에서 프로세스이름 부분에 와일드카드 문자나 정규표현식도 사용 가능합니다. 

 

grep -v 은 지정된 패턴과 일치하는 항목을 제외할 때 사용합니다.

여기서는 "grep 프로세스이름" 도 하나의 명령어기 때문에 "ps -ef | grep 프로세스이름" 의 결과에 포함되는데 kill 명령어에서 이를 제외시키기 위함입니다.

 

awk 명령어는 입력 값(record : 레코드)을 공백 문자로 분리하여 필드(field) 단위로 처리 할 수 있게 해주는 명령어입니다. 

여기서는 ps -ef 명령어의 결과에서 PID를 추출하기 위해 사용됩니다. 

 

마지막으로 `명령어` 백틱(backticks)은 치환 명령어라고도 하는데 ` ` 로 감싼 부분의 실행 결과로 치환됩니다. 

 

결과적으로 kill `ps -ef | grep 프로세스이름 | grep -v grep | awk '{print $2}'` 에서 `ps -ef | grep 프로세스이름 | grep -v grep | awk '{print $2}'` 부분이 실행된 결과로 치환되어 kill PID 형태로 실행이 되는 것입니다. 

 

 

참고

 

 

[Linux] 파이프(pipe)에 대한 이해

안녕하세요 피터입니다. 오늘은 리눅스의 파이프(pipe)에 대해서 설명드리겠습니다. 파이프는 재지향(redirection)과 더불어 리눅스의 명령어들을 훨씬 강력하게 무장시켜주는 역할을 하는 핵심 기�

gracefulprograming.tistory.com

 

[Linux] ps 로 실행 중인 프로세스 확인하기

개요 ps 명령어는 리눅스에서 현재 실행중인 프로세스를 확인하는 명령어 입니다. Process Status에서 따온 이름이죠. 이름 그대로 명령어를 실행하면 현재 실행되고 있는 프로세스들의 정보를 화면

gracefulprograming.tistory.com

 

-Peter의 우아한 프로그래밍

여러분의 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

개요

 

ps 명령어는 리눅스에서 현재 실행중인 프로세스를 확인하는 명령어 입니다.

 

Process Status에서 따온 이름이죠. 이름 그대로 명령어를 실행하면 현재 실행되고 있는 프로세스들의 정보를 화면에 출력합니다. 

 

윈도우에서 특정 프로세스가 실행 중인지 확인하거나 강제 종료하기 위해 작업 관리자를 사용하듯이 리눅스에서는 ps 명령어가 자주 사용됩니다.

특히 GUI를 사용하지 않는 서버 환경에서는 대부분의 프로세스들이 백그라운드에서 동작하기 때문에 특정 프로세스가 동작 중인지 확인하기 위해서 많이 쓰입니다. 

 

bash 스크립트(script)를 통한 자동화에도 ps 명령어가 자주 사용되는데요. 

주로 특정 프로세스에 시그널(signal)을 보내야 할 때 PID(process id)를 식별하기 위해서 쓰이기도 하고, 프로세스가 중단되면 다시 실행시키기 위해 감시하는 목적으로 쓰이기도 합니다. 

 

사용법

ps --help all

shell에서 위와 같이 help 명령어를 실행하면 ps 명령어에 대한 자세한 도움말을 볼 수 있습니다. 

Usage:
 ps [options]

Basic options:
 -A, -e               all processes
 -a                   all with tty, except session leaders
  a                   all with tty, including other users
 -d                   all except session leaders
 -N, --deselect       negate selection
  r                   only running processes
  T                   all processes on this terminal
  x                   processes without controlling ttys

Selection by list:
 -C <command>         command name
 -G, --Group <GID>    real group id or name
 -g, --group <group>  session or effective group name
 -p, p, --pid <PID>   process id
        --ppid <PID>  parent process id
 -q, q, --quick-pid <PID>
                      process id (quick mode)
 -s, --sid <session>  session id
 -t, t, --tty <tty>   terminal
 -u, U, --user <UID>  effective user id or name
 -U, --User <UID>     real user id or name

  The selection options take as their argument either:
    a comma-separated list e.g. '-u root,nobody' or
    a blank-separated list e.g. '-p 123 4567'

Output formats:
 -F                   extra full
 -f                   full-format, including command lines
  f, --forest         ascii art process tree
 -H                   show process hierarchy
 -j                   jobs format
  j                   BSD job control format
 -l                   long format
  l                   BSD long format
 -M, Z                add security data (for SELinux)
 -O <format>          preloaded with default columns
  O <format>          as -O, with BSD personality
 -o, o, --format <format>
                      user-defined format
  s                   signal format
  u                   user-oriented format
  v                   virtual memory format
  X                   register format
 -y                   do not show flags, show rss vs. addr (used with -l)
     --context        display security context (for SELinux)
     --headers        repeat header lines, one per page
     --no-headers     do not print header at all
     --cols, --columns, --width <num>
                      set screen width
     --rows, --lines <num>
                      set screen height

Show threads:
  H                   as if they were processes
 -L                   possibly with LWP and NLWP columns
 -m, m                after processes
 -T                   possibly with SPID column

Miscellaneous options:
 -c                   show scheduling class with -l option
  c                   show true command name
  e                   show the environment after command
  k,    --sort        specify sort order as: [+|-]key[,[+|-]key[,...]]
  L                   show format specifiers
  n                   display numeric uid and wchan
  S,    --cumulative  include some dead child process data
 -y                   do not show flags, show rss (only with -l)
 -V, V, --version     display version information and exit
 -w, w                unlimited output width

        --help <simple|list|output|threads|misc|all>
                      display help and exit

 

가장 흔하게 사용되는 옵션은 -e-f 옵션입니다. 

-e 옵션은 전체 프로세스를 보겠다는 옵션인데 이 옵션을 사용하지 않으면 현재 로그인된 shell에서 실행 중인 프로세스만 표시되기 때문에 대부분의 경우 -e 옵션을 사용합니다. 

-A 로도 동일하게 사용 가능하지만 대문자 A보다는 소문자 e가 사용하기 좀 더 편합니다.

[root@peterdev ~]# ps
  PID TTY          TIME CMD
  756 pts/0    00:00:00 ps
23316 pts/0    00:00:01 bash
28992 pts/0    00:00:00 bash

 

[root@peterdev ~]# ps -e
  PID TTY          TIME CMD
    1 ?        01:16:34 systemd
    2 ?        00:00:14 kthreadd
    3 ?        00:01:10 ksoftirqd/0
    5 ?        00:00:00 kworker/0:0H
    7 ?        00:00:00 migration/0
    8 ?        00:00:00 rcu_bh

...
...
...

 4982 ?        00:02:04 python
31680 ?        00:01:10 qmgr
32662 ?        00:00:00 pickup

 

-f 옵션은 프로세스의 상태 정보를 모두(full) 표시하겠다는 옵션입니다. 

[root@peterdev ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root       773 28992  0 15:51 pts/0    00:00:00 ps -f
root     23316 23302  0  2018 pts/0    00:00:01 /bin/bash
root     28992 28988  0 08:25 pts/0    00:00:00 -bash

프로세스를 실행한 UIDPID, 그리고 부모 프로세스의 ID(PPID)도 표시되며, 프로세스를 실행할 당시의 커맨드라인(command-line)도 표시 되기 때문에 자주 사용됩니다. 

같은 이름의 프로세스가 여러 개 실행 중일 때는 커맨드 라인으로 프로세스를 구분해야 하기 때문입니다. (nginx 같은 경우)

여러 개의 옵션을 함께 사용할 때는 - 기호는 한번만 써도 됩니다.

[root@peterdev ~]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0  2018 ?        01:16:34 /usr/lib/systemd/systemd --system --deserialize 21
root         2     0  0  2018 ?        00:00:14 [kthreadd]
root         3     2  0  2018 ?        00:01:10 [ksoftirqd/0]
root         5     2  0  2018 ?        00:00:00 [kworker/0:0H]
root         7     2  0  2018 ?        00:00:00 [migration/0]

 

-u 옵션을 사용하면 특정 사용자 계정(UID)으로 실행되고 있는 프로세스만 볼 수 있습니다. 

[root@localhost ~]# ps -fu node_manager
UID         PID   PPID  C STIME TTY          TIME CMD
node_ma+ 183483      1  0  8월04 ?      00:09:22 java -jar -Xms2048m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m node_manager-0.0.1-SNAP
node_ma+ 199085 199067  0  8월03 ?      00:00:00 sshd: node_manager@pts/3
node_ma+ 199086 199085  0  8월03 pts/3  00:00:00 -bash

 

 

예제

ps -ef 명령어를 실행해보시면 아시겠지만 결과가 굉장히 많이 출력됩니다. 

당연하게도 전체 프로세스 정보를 출력하는 것이니 많을 수밖에요. 이렇다 보니 내가 확인하려고 하는 프로세스를 찾기 위해서는 뭔가 방법이 필요해 보입니다. 

이전 포스팅에서 파이프(pipe)에 대해서 설명드린 적이 있는데요, 바로 지금이 파이프를 활용할 때입니다. 

[Linux] 파이프(pipe)에 대한 이해

 

만일 내가 찾고자 하는 프로세스 이름에 python 이라는 문자열이 들어간다고 하면 아래와 같이 찾을 수 있습니다. 

[root@peterdev ~]# ps -ef | grep python
root       477     1  0  2018 ?        00:00:47 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid
root       819     1  0  2018 ?        03:10:37 /usr/bin/python -Es /usr/sbin/tuned -l -P
root       847 28992  0 16:01 pts/0    00:00:00 grep --color=auto python
root      4982  4956  0  2019 ?        00:02:04 python ./get_pr.py
root     28692     1  0  2019 ?        00:00:00 python app.py

이처럼 python 프로세스가 여러 개 실행 중인걸 확인 할 수 있는데요, 커맨드라인을 통해 어떤 스크립트를 실행하고 있는지 구별이 가능합니다.

 

 

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

 

개요

 

안녕하세요 피터입니다.

오늘은 C# 프로젝트 log4net 라이브러리를 통해 로그 시스템(log system)을 적용하는 방법을 알려드리겠습니다.

 

프로젝트를 진행하다 보면 로그 시스템이 절실할 때가 있습니다. 

특정 상황에서 에러가 발생하는데 어떤 절차에 의해 실행하면 에러가 발생하는지 추적이 어려울 때가 바로 그런 경우입니다. 

또한 개발을 진행할 때에는 세부적이고 자세한 로그를 기록하는 것이 도움이 되지만 실제로 사용자가 프로그램을 쓸 때에는 중요한 내용만 기록하는 것이 더 도움이 됩니다. 

이런 이유로 상황에 따라 어떤 수준의 로그를 기록할지 손쉽게 설정할 수 있는 로그 시스템이 필요합니다.

C# 프로젝트에서는 log4net 라이브러리를 통해 이러한 기능을 쉽게 구현 할 수 있습니다.

 

로그시스템 적용

log4net 라이브러리 설치

Visual studio 에서는 nuget을 통해 손쉽게 라이브러리를 설치 할 수 있습니다.

먼저 로그시스템을 적용하고자 하는 프로젝트를 열고 nuget에서 log4net을 검색합니다.

nuget 에서 log4net (Apach Software Foundation)을 검색하여 적용하고자 하는 프로젝트에 설치합니다.

 

 


log4net 환경설정 파일 추가

 

로그 시스템을 프로젝트에 원하는대로 적용하기 위해서는 환경설정 파일이 필요합니다.

 

[프로젝트] - [추가] - [새 항목] 클릭

 

"구성" 을 키워드로 검색하여 "응용 프로그램 구성 파일" (확장자가 config)을 선택합니다. 

 

파일명을 log4net.config 로 변경한 다음 추가를 클릭합니다.

(파일명은 원하는대로 변경이 가능합니다. 설정파일을 읽어오는 부분도 동일하게 변경해주시면 됩니다.)

 

예제) log4net.config 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <log4net>
    <root>
      <level value="ALL"/>
      <appender-ref ref="console"/>
      <appender-ref ref="file"/>
      <appender-ref ref="fatal_file"/>
    </root>
    <appender name="console" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %level %logger - %message%newline" />
      </layout>
    </appender>
    <appender name="file" type="log4net.Appender.RollingFileAppender">
      <file value="log\app.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="5" />
      <maximumFileSize value="100MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %level %logger - %message%newline" />
      </layout>
    </appender>
    <appender name="fatal_file" type="log4net.Appender.RollingFileAppender">
      <file value="log\fatal.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="5" />
      <maximumFileSize value="100MB" />
      <staticLogFileName value="true" />
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="FATAL" />
        <param name="LevelMax" value="FATAL" />
      </filter>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %level %logger - %message%newline" />
      </layout>
    </appender>
  </log4net>
</configuration>

 

log4net의 설정에서는 appender가 핵심적인 역할을 수행합니다. 

appender의 종류에 따라 화면에 출력하거나 파일에 기록할 수 있으며, 이메일로도 로그를 전송할 수 있습니다.

위에서 사용한 appender는 아래 2가지 입니다.

 

log4net.Appender.ConsoleAppender (화면에 출력하는 appender)

log4net.Appender.RollingFileAppender (파일에 기록하는 appender)

 

RollingFileAppender에 대한 위 설정은 설치파일경로\log\app.log 파일로 로그를 기록하며, 100MB 초과 시 5개까지 파일을 분기하는 설정입니다. 

 

<root>
  <level value="ALL"/>
  <appender-ref ref="console"/>
  <appender-ref ref="file"/>
  <appender-ref ref="fatal_file"/>
</root>

 

appender 들을 설정했으면 root 태그에 appender-ref 를 추가해줍니다. 

log4net 라이브러리에서 발생한 로그들을 appender-ref 목록에 있는 appender 들에게 전달해주는데 만일 level을 지정하면 지정된 레벨 이상의 로그만 appender 로 전달할 수 있습니다.

레벨에 설정 가능한 값

  • ALL
  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL

 

appender 별로 로그 레벨을 지정하고자 할 때는 아래와 같이 appender 내부에 filter를 사용하면 됩니다. 

<filter type="log4net.Filter.LevelRangeFilter">
    <param name="LevelMin" value="FATAL" />
    <param name="LevelMax" value="FATAL" />
</filter>

 

 

환경설정을 다 작성하였으면 log4net.config 파일 속성의 고급에서 아래 내용을 변경해줍니다.

아래 속성을 지정해주지 않으면 빌드 시 환경설정 파일이 출력디렉토리에 복사되지 않기 때문에 실행했을 때 환경설정을 읽어오지 못하게 되니 반드시 적용해주세요.

빌드작업 - 내용

출력 디렉터리에 복사 - 항상 복사 ("새버전이면 복사"로 해도 무방합니다)

 


 

환경설정 파일 로드

AssemblyInfo.cs

 

프로젝트에서 환경설정 파일을 읽어올 수 있도록 AssemblyInfo.cs 파일 하단에 코드를 추가해줍니다.

위에서 파일 이름을 변경했다면 변경한 파일 이름으로 작성해주셔야 합니다.

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

Watch = true 라는 옵션을 주면 프로그램 실행 중에 config 파일을 수정해도 바로 반영됩니다.

이 옵션은 상당히 유용한 옵션으로 프로그램을 재실행하지 않고도 로그 시스템의 설정을 바꿀 수 있기 때문에 사용을 권장드립니다.


로그 인터페이스 생성 및 로그 기록 

App.xaml.cs

using log4net;

    public partial class App : Application
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(App));

        protected override void OnStartup(StartupEventArgs e)
        {
            log.Info("=============  Started application  =============");
            base.OnStartup(e); 
        }
    }

App.xaml.cs 에 log4net 라이브러리를 추가하고 프로그램이 시작 할 때 로그를 기록해봤습니다.

LogManager의 GetLogger 메소드를 통해 ILog 인터페이스를 얻어올 수 있습니다. 

GetLogger에 파라미터로 넘겨주는 이름이 나중에 로그에 기록될 때 식별자로 기록됩니다.

 

이렇게 얻은 ILog 인터페이스를 통해 로그를 기록 할 수 있습니다. 

 

private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
 
public MainWindow()
{
    InitializeComponent();
}
 
private void Button_Click(object sender, RoutedEventArgs e)
{
    log.Debug("Debug");
    log.Info("Info");
    log.Warn("Warn");
    log.Error("Error");
    log.Fatal("Fatal");
}

ILog 인터페이스는 위와같이 레벨별 메소드들을 제공합니다.

 

작은 팁을 드리면 Error 로그는 아래와 같이 Exception 객체를 파라미터로 넘겨서 출력할 수 있습니다.

try
{
    ;
}
catch (Exception ex)
{
    log.Error($"An exception occurred from {MethodBase.GetCurrentMethod().Name}", ex);
}

비즈니스 로직이 들어가는 코드를 위와 같이 작성해두면 나중에 에러가 발생했을 때 추적이 쉬워집니다. 

 


로그 기록 확인

위에서 설정한 대로 실행파일이 위치한 디렉토리 하위에 log 라는 디렉토리와 로그파일이 생성된 것을 확인 할 수 있습니다.

로그 파일을 열어보면 레벨별로 잘 기록된 것을 볼 수 있습니다.

 

 

 

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

 

 

'프로그래밍 > C#' 카테고리의 다른 글

[C#] 프로젝트에 log4net 적용하기  (7) 2019.11.01
블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

 

개요

 

안녕하세요 피터입니다.

오늘은 vim에서 대문자 또는 소문자로 일괄적으로 변경하는 방법에 대해서 알려드리겠습니다. 

전체적으로 대문자 또는 소문자로 변경하는 것도 가능하지만 대문자인 것은 소문자로 소문자인 것은 대문자로 반전(inverse)하는 것도 가능합니다. 

 

vim 에서 대소문자를 변경하는 방법은 크게 두 가지가 있습니다. 

첫 번째는 일반 모드에서 명령어를 입력해서 변경하는 방법이고, 

두 번째는 비주얼 모드에서 영역을 지정하고 변경하는 방법입니다.

 

일반 모드

일반 모드에서 대소문자를 변경하는 명령어는 아래와 같습니다.

Command Description Examples
gU<motion>

<motion> 에 해당하는 범위의 문자를

대문자로 변경

gUU : 현재 라인 전체 대문자로 변경

gUw : 현재 커서부터 단어의 끝까지 대문자로 변경

gUaw : 현재 단어 전체 대문자로 변경

gUG : 현재 라인부터 문서의 끝까지 대문자로 변경

gu<motion>

<motion> 에 해당하는 범위의 문자를

소문자로 변경

guu : 현재 라인 전체 소문자로 변경

guw : 현재 커서부터 단어의 끝까지 소문자로 변경

guaw : 현재 단어 전체 소문자로 변경

guG : 현재 라인부터 문서의 끝까지 소문자로 변경

g~<motion>

<motion> 에 해당하는 범위의 문자를

반전시킴

g~~ : 현재 라인 전체 대소문자 반전

g~w : 현재 커서부터 단어의 끝까지 대소문자 반전

g~aw : 현재 단어 전체 대소문자 반전

g~G : 현재 라인부터 문서의 끝까지 대소문자 반전

기본적으로 gU, gu 명령어는 현재 커서 위치를 시작으로 <motion> 에 지정된 범위까지 영향을 받게 됩니다.

 

아래 gU 명령어 사용 예제를 참고해주세요. (gu, g~ 명령어도 동일한 원리로 동작합니다)

gUU 실행 결과

 

gUw 실행 결과

 

gUaw 실행 결과

 

gUG 실행 결과

 

비주얼 모드

비주얼 모드에서 영역을 지정한 다음에 U 키(shift + u)를 누르면 영역 내의 문자들이 대문자로 변경됩니다.

마찬가지로 영역 지정 후 u 키를 누르면 소문자로, ~ 키를 누르면 대소문자 반전이 됩니다.

비주얼 모드에서 영역 지정 후 shift + u 실행 결과

 

 

 

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 남겨주시면 감사하겠습니다.

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요