추상 자료형 (Abstract Data Type)

 간단히생각하면 클래스의 기능과도 같다.

단 자료구조 상에서 추상 자료형은 data handling 이라고말할 수 있다.

단순히 생각했을 때 데이터의 삽입과 추출과 같은 데이터를 가지고 연산하는 기능이무엇인지를 나열 한 것을 추상자료 형이라고 한다.

기본적으로 추가, 수정, 삭제. 조회, 삽입 등이 있다

추상자료 형이라고 해서 int, char 와 같은 자료형을 말하는것이 아니라 구조체 혹은 클래스와 같은 자료형의 정의에 기능 or 연산과 관련된 내용을 명시할 수 있다.

어렵게 생각하지 말고 해당 자료구조를 가지고 어떻게 다루어야 하는지 간단히 명시하자

추상자료형 학습 순서

  1. 자료구조의 ADT를 정의한다 (구조체 or 클래스 / 기능or 연산)
  2. ADT를 근거로 해당 자료구조를 활용하는 main 함수를 정의한다.
  3. ADT를 근거로 해당 자료구조를 구현한다.

 

  • 질문: 추상자료형을 실무에서 어떻게 사용해야될까?

'Computer Science > 자료구조' 카테고리의 다른 글

C++ 기반  (0) 2022.10.02
리스트 구현 문제  (0) 2022.10.02
자료구조의 이해(1)  (0) 2017.10.21
// Main 함수
 
#include <stdio.h>
#include <stdlib.h>
 
#include "linkedArrayList.h"
 
int main(void)
{
 // 메뉴 입력 번호 변수
 int inputNumber;
 CString str;
 // List 생성 및 초기화
 arrayList* list = new arrayList();
 list->ListInit();
 
  while(1)
  {
   printf("\n메뉴: 0-종료 1-출력 2-추가 3-검색 4-삭제\n");
   printf("입력 : " );
   scanf("%d", &inputNumber);
 
   switch (inputNumber)
   {
   case 0:
    {
     printf("종료합니다");
    delete list;
     return 0;
    }
   case 1:
    {
     list->LPrint();
     break;
    }
   case 2:
    {
    char inputString[LIST_LEN] = "";    
 
    printf("저장하려는 문자를 입력하세요\n");
    printf("입력: ");
    scanf("%s", inputString);
 
    str = inputString;
    
     list->LInsert(str);
     break;
    }
  case 3:
   {
    char inputString[LIST_LEN] = ""; 
 
    printf("검색할 문자열 입력: ");
    scanf("%s", inputString);  
    str = inputString;
 
    list->LSearch(str);    
    break;
   }
  case 4:
   {
    char inputString[LIST_LEN] = ""; 
 
    printf("삭제할 문자열 입력: ");
    scanf("%s", inputString); 
    str = inputString;
 
    list->LRemove(str);   
    break;
   }
   default:
    continue;
   }
  }
 
 return 0;
}
 
// linkedList.h 파일
#ifndef __ARRAY_LIST_H__
#define __ARRAY_LIST_H__
 
#include "atlstr.h"
 
#define TRUE 1
#define FALSE 0
#define LIST_LEN 100
 
typedef struct _STRING
{
 CString str;
 struct _STRING* next;
} STRING;
 
typedef STRING List;
 
class arrayList
{
public:
 arrayList();
 ~arrayList();
 
 void ListInit();  
 char* LPrint();
 List* LNext(List* list);
  void LInsert(CString str);
 char* LSearch(CString& searchString);
  int findSubString(CString& str, CString& findStr);
 void LRemove(CString& searchString);
 //List* nextFind(List* nextNode);
 
private:
 List * head;
 List * cur;
 List * before;
 int numOfData;
 
};
 
#endif
 
// linkedList.cpp 파일
 
 
#include <stdio.h>
#include "linkedArrayList.h"
 
arrayList::arrayList()
{
 head = NULL;
 cur = NULL;
 before = NULL;
 numOfData = 0;
}
 
arrayList::~arrayList()
{
 
}
 
void arrayList::ListInit()
{
  head = new List();
   head->next = NULL;
 head->str = "";
   cur = NULL;
   before = head; 
   before->next = NULL;
}
 
char* arrayList::LPrint()
{
 List* nextNode = head->next;
 
 while(1)
 {
  if(nextNode != NULL )
  {
   if( nextNode->next == NULL )
   {
    wprintf(_T("%s "), nextNode->str);
    return 0;
   }
   else
   {
    wprintf(_T("%s "), nextNode->str);
    nextNode = LNext(nextNode);      
   }   
  }
  else
   break;
 }
 
 return 0;
}
 
List* arrayList::LNext(List* list)
{
 List* nextNode = list->next;
 
 return nextNode;
}
 
void arrayList::LInsert(CString str)
{
 List* newNode = new List;                       // 다음번 노드를 동적할당 하고
 
 if( numOfData >= LIST_LEN)
 {
  puts("저장할 용량을 초과했습니다");
  delete newNode;
  return;
 } 
 
 newNode->next = head->next;                                  // 새로 생긴 공간의 다음에 널을 만든다
 newNode->str = str;
 head->next = newNode; 
 cur = newNode;  
 
 numOfData++;
}
 
char* arrayList::LSearch(CString& str)
{
 List* nextNode = head->next;
 int pFind;
 
 while(1)
 {
  if(nextNode != NULL)
  {
   if( nextNode->next == NULL )
   {   
    pFind = findSubString(nextNode->str, str);
 
    if( !pFind )
    {
     wprintf(_T("%s"), nextNode->str);
    }
    return 0;
   }
   else
   {
    pFind = findSubString(nextNode->str, str);    
 
    if( !pFind )
    {
     wprintf(_T("%s"), nextNode->str);
    }   
    nextNode = LNext(nextNode);
   }
  }
  else
   break;
 }
 return 0;
}
 
int arrayList::findSubString(CString& str, CString& searchStr)
{
 int ret;
 ret = str.Compare(searchStr);
 
 return ret; 
}
 
void arrayList::LRemove(CString& searchString)
{
 List* nextNode = head->next;
 int pFind;
 
 while(1)
 {
  if(nextNode != NULL)
  {
   pFind = findSubString(nextNode->str, searchString);
 
   if( !pFind )
   {
    cur = nextNode->next;
    delete nextNode;
    nextNode = NULL;
    before->next = cur;
    (numOfData)--;
    nextNode = LNext(before);
   }
   else
   {
    before = nextNode;
    nextNode = LNext(nextNode);
 
    if( nextNode == NULL)
     return;   
   }
  }
  else
   break;
  
 }
 
 return;
}
 
// List* arrayList::nextFind(List* nextNode)
// {
//  List* next = nextNode->next;
//
//  if( next != NULL)
//   return next;
//
//  return 0;
// }

'Computer Science > 자료구조' 카테고리의 다른 글

추상자료형이란?  (0) 2022.10.02
리스트 구현 문제  (0) 2022.10.02
자료구조의 이해(1)  (0) 2017.10.21
Struct STRING
{
        Char string[100];
        STRING* pstring;
}
 
위와 같은 구조체 사용하세요
 

포인터 배열을 사용해서, 문자열에 대해 다음과 같은 메뉴를 처리합니다.
 
     메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
 
   출력 : 지금까지 입력한 모든 문자열을 출력합니다.
   추가 : 새로운 문자열을 포인터 배열에 추가합니다.
   검색 : 검색할 문자열을 포함하고 있는 모든 문자열을 출력합니다.
   삭제 : 삭제할 문자열을 포함하고 있는 모든 문자열을 삭제합니다.
 
   이 문제는 입력할 문자열의 개수와 문자열의 길이가 정해져 있지않다는 점에서 어렵습니다.
   또한 검색과 삭제에서 완전히 똑같은 문자열이 아니라 부분 문자열을 처리하는 것도 어렵습니다.
 
   [입출력]
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 2
   단어 : bonus
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 2
   단어 : big_money
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 2
   단어 : rainbow
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 2
   단어 : handshaking
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 2
   단어 : switch_on
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 1
   bonus big_money rainbow handshaking switch_on
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 3
   단어 : on
   bonus big_money switch_on
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 4
   단어 : on
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 1
   handshaking rainbow
   메뉴 : 0. 종료  1. 출력  2. 추가  3. 검색  4. 삭제
   선택 : 0

 

 

 

 

 

Main.c 파일
 
#include <stdio.h>
#include <stdlib.h>
 
#include "linkedArrayList.h"
#include "stringData.h"
 
int main(void)
{
 // 메뉴 입력 번호 변수
 int inputNumber;
 // List 생성 및 초기화
 lList list;
 List * pString;
 char* nullString = 0;
 ListInit(&list);
 
 while(1)
 {
  printf("\n메뉴: 0-종료 1-출력 2-추가 3-검색 4-삭제\n");
  printf("입력 : " );
  scanf("%d", &inputNumber);
 
  switch (inputNumber)
  {
  case 0:
   {
    printf("종료합니다");
    return 0;
   }
  case 1:
   {
    LPrint(list.head);
    break;
   }
  case 2:
   {
    pString = makeString();
    LInsert(&list, pString);
    break;
   }
  case 3:
   {
    printf("검색할 문자열 입력: ");
    scanf("%s", &nullString);   
 
    LSearch(list.head, &nullString);
    nullString = 0;
    break;
   }
  case 4:
   {
    printf("삭제할 문자열 입력: ");
    scanf("%s", &nullString); 
 
    LRemove(&list, &nullString);
    nullString = 0;
    break;
   }
  default:
   continue;
  }
 }
 
 return 0;
}
 
LinkedArrayList.h 파일
#ifndef __ARRAY_LIST_H__
#define __ARRAY_LIST_H__
 
#include "stringData.h"
 
#define TRUE 1
#define FALSE 0
 
typedef struct _linkedList
{
 List * head;
 List * cur;
 List * before;
 int numOfData;
} linkedList;
 
typedef linkedList lList;
 
// 해당 문제의 ADT
void ListInit(lList * plist);    
// 초기화할 리스트의 주소값을 인자로 전달
// 리스트 생성 후 제일 먼저 호출되어야 하는 함수이다
char* LPrint(List *plist);
// 저장된 문자열 모두를 출력한다
void LInsert(lList * plist); //, LData data);
// 리스트에 데이터를 저장한다. 매개변수 data에 전달된 값을 저장한다
char* LSearch(List * plist, char * searchString);
char* findSubString(char* str, char* findStr); //, char* searchStr)
// 리스트에서 특정 문자열을 찾아서 찾은 문자열을 반환한다
/*int LFirst(List * plist, LData * pdata);*/
// 첫번째 데이터가 pdata가 가리키는 메모리에 저장된다
// 데이터의 참조를 위한 초기화가 진행된다
// 참조 성공시 True 실패시 False를 반환
/*int LNext(List * plist, LData * pdata);*/
// 참조된 데이터의 다음 데이터가 pdata가 가리키는 메모리에 저장된다
// 순차적인 참조를 위해서 반복 호출이 가능하다
// 참조를 새로 시작하려면 먼저 LFirst 함수를 호출한다
// 참조 성공 시 True 실패시 False를 반환
void LRemove(lList * plist, char * searchString);
List* nextFind(List* nextNode);
// LFirst 또는 LNext 함수의 마지막 반환 데이터를 삭제 한다
// 삭제된 데이터는 반환된다
// 마지막 반환 데이터를 삭제하므로 연이은 반복 호출을 허용하지 않는다
/*int LCount(List * plist);*/
// 리스트에 저장되어 있는 데이터의 수를 반환한다
 
#endif
 
stringData.h 파일
 
#ifndef __NAME_CARD_H__
#define __NAME_CARD_H__
 
#define LIST_LEN 100
 
typedef struct _STRING
{
 char stringData[LIST_LEN];
 struct _STRING* next;
} STRING;
 
typedef STRING List;
 
List* makeString();
 
#endif

 

// LinkedArrayList.c 파일
 
#include <stdio.h>
#include "linkedArrayList.h"
 
void ListInit(lList *pList)
{
 pList->head = (List *)malloc(sizeof(List));
 pList->head->next = NULL;
 memset(pList->head->stringData, 0, sizeof(pList->head->stringData));
 //pList->head->stringData[LIST_LEN] = {0};
 pList->cur = NULL;
 pList->before = pList->head; 
 pList->before->next = NULL;
 pList->numOfData = 0;
}
 
char* LPrint(List *plist)
{
 List* nextNode = plist->next;
 if(nextNode)
 {
  if( nextNode->next == NULL )
  {
   printf("%s ", nextNode->stringData);
   return 0;
  }
  else
  {
   LPrint(nextNode); 
   printf("%s ", nextNode->stringData);
  }
 }
 
 return 0;
}
 
void LInsert(lList * plist, List* pStringData)
{
 List* newNode = (List *)malloc(sizeof(List));                       // 다음번 노드를 동적할당 하고
 strcpy(newNode->stringData, pStringData->stringData);
 
 if( plist->numOfData >= LIST_LEN)
 {
  puts("저장할 용량을 초과했습니다");
  free(newNode);
  return;
 } 
 
 newNode->next = plist->head->next;                                  // 새로 생긴 공간의 다음에 널을 만든다
 plist->head->next = newNode; 
 plist->cur = pStringData;  
 
 plist->numOfData++;
}
 
char* LSearch(List * plist, char * searchString)
{
 List* nextNode = plist->next;
 char *pFind;
 
 if(nextNode)
 {
  if( nextNode->next == NULL )
  {   
   pFind = findSubString(nextNode->stringData, searchString);
 
   if( !pFind )
   {
    printf("%s", nextNode->stringData);
   }
 
   return 0;
  }
  else
  {
   LSearch(nextNode, searchString); 
 
   pFind = findSubString(nextNode->stringData, searchString);
 
   if( !pFind )
   {
    printf("%s", nextNode->stringData);
   }   
  }
 }
 
 return 0;
}
 
char* findSubString(char* str, char* searchStr)
{
 if( strstr(str, searchStr) != NULL )
  return 0;
 else
  return -1;
}
void LRemove(lList * plist, char * searchString)
{
 List* nextNode = plist->head;
 char *pFind;
 
 while(1)
 {
  //if( nextNode->stringData == NULL)
  // return;
 
  pFind = findSubString(nextNode->stringData, searchString);
 
  if( !pFind )
  {
   plist->cur = nextNode->next;
   free(nextNode);
   nextNode->stringData[0] = 0;
   plist->before->next = plist->cur;
   (plist->numOfData)--;
   nextNode = nextFind(plist->before);
  }
  else
  {
   plist->before = nextNode;
   nextNode = nextFind(nextNode);
 
   if( nextNode == NULL)
    return;   
  }
 }
 
 return;
}
 
List* nextFind(List* nextNode)
{
 List* next = nextNode->next;
 
 if( next != NULL)
  return next;
 
 return 0;
}
 
// stringData.c 파일
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "stringData.h"
 
List * makeString()
{
 char inputString[LIST_LEN] = "";
 List * newString = (List *)malloc(sizeof(List));
 
 printf("저장하려는 문자를 입력하세요\n");
 printf("입력: ");
 scanf("%s", inputString);
 
 strcpy(newString->stringData, inputString);
 return newString;
}

 

'Computer Science > 자료구조' 카테고리의 다른 글

추상자료형이란?  (0) 2022.10.02
C++ 기반  (0) 2022.10.02
자료구조의 이해(1)  (0) 2017.10.21

재귀함수 문제

1. 횟수를 입력받은 다음, 입력받은 횟수만큼 "hello" 문자열을 출력하는 재귀함수를 만드세요.

[입력]

횟수 : 4

[출력]

결과 : hello hello hello hello

 

2. 대문자 A에서 Z까지 출력하는 재귀함수를 만드세요.

그리고 Z에서 A까지 거꾸로 출력하는 재귀함수도 만드세요.

[입력]

없음

[출력]

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Z Y X W V U T S R Q P O N M L K J I H G F E D C B A

 

3. 문자열을 출력하는 재귀함수를 만드세요.

이때 문자열은 scanf 함수로 입력받도록 합니다.

[입력]

문자열 : This_is_a_recursive_call.

[출력]

결과 : This_is_a_recursive_call.

 

4. 범위를 표현하는 구조체가 있습니다.

struct RANGE

{

int from, to;

};

RANGE 구조체에 입력한 범위 안에 포함된 모든 정수를 더하는 재귀함수를 만듭니다.

from과 to 멤버 또한 범위에 포함되는 것으로 처리합니다.

[입력]

범위 : 5 11

[출력]

합계 : 56

 

5. 크기가 15인 int 배열을 선언하고, 0부터 11 사이의 난수로 채웁니다.

그리고 배열 내의 위치를 가리키는 정수를 입력받아서,

자신을 포함한 인접한 한 자리 정수들을 모두 -1로 바꾸는 재귀함수를 만듭니다.

[입력]

위치 : 7

[출력]

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

=============================================

3 5 8 10 5 2 0 0 8 3 11 9 1 8 0

3 5 8 10 -1 -1 -1 -1 -1 -1 11 9 1 8 0

 

 

재귀함수 문제

1. 횟수를 입력받은 다음, 입력받은 횟수만큼 "hello" 문자열을 출력하는 재귀함수를 만드세요.

[입력]

횟수 : 4

[출력]

결과 : hello hello hello hello

 

답: Qt

 

void printHello(int n)

{

if( n == 0)

return ;

else

{

qDebug("hello" );

printHello(n-1);

}

}

 

int main(int argc, char *argv[])

{

QApplication a(argc, argv);

printHello(4);

return a.exec();

}

 

void main()

{

int count;

 

printf( "횟수 : " );

scanf( "%d", &count );

 

printf( "결과 : " ); PrintHello( count );

printf( "\n" );

}

 

void PrintHello( int count )

{

if( count > 0 )// 리턴 조건을 다름

{

printf( "hello " );

PrintHello( count-1 );

}

}

 

2. 대문자 A에서 Z까지 출력하는 재귀함수를 만드세요.

그리고 Z에서 A까지 거꾸로 출력하는 재귀함수도 만드세요.

[입력]

없음

[출력]

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Z Y X W V U T S R Q P O N M L K J I H G F E D C B A

 

void printAlfabet(int n)

{

if( n < 65 || n > 90)

return ;

else

{

qDebug("%c", n);

printHello(n+1);

}

}

 

int main(int argc, char *argv[]){

QApplication a(argc, argv);

printAlfabet(65);

return a.exec();

}

 

역으로 출력하는 것은 초기값을 -1 시키면 된다.

 

void PrintUpper( char ch );

void PrintUpperRev( char ch );

 

 

void main()

{

PrintUpper( 'A' ); printf( "\n" );

PrintUpperRev( 'A' ); printf( "\n" );

}

 

void PrintUpper( char ch )

{

if( ch > 'Z' )

return;

 

printf( "%c ", ch );

PrintUpper( (char) (ch+1) );

}

 

void PrintUpperRev( char ch )

{

if( ch > 'Z' )

return;

 

PrintUpperRev( (char) (ch+1) );

printf( "%c ", ch );

}

 

3. 문자열을 출력하는 재귀함수를 만드세요.

이때 문자열은 scanf 함수로 입력받도록 합니다.

[입력]

문자열 : This_is_a_recursive_call.

[출력]

결과 : This_is_a_recursive_call.

 

void printStr(char* str, int nStart, int nEnd)

{

if(nEnd == nStart)

return

else

{

printf("%c",str[nStart]);

printStr(str, nStart+1, nEnd);

}

}

 

 

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

char str[20];

printf("문자열을 입력하세요. \n");

scanf("%s", str);

int len = strlen(str);

printStr(str, 0, len);

return a.exec();

}

 

#include <stdio.h>

 

void PrintString( char* str );

 

void main()

{

char str[256];

 

printf( "문자열 : " );

scanf( "%s", str );

 

printf( "결과 : " ); PrintString( str );

printf( "\n" );

}

 

void PrintString( char* str )

{

if( *str == '\0' )

return;

 

putchar( *str );

PrintString( str+1 );

}

 

4. 범위를 표현하는 구조체가 있습니다.

struct RANGE

{

int from, to;

};

RANGE 구조체에 입력한 범위 안에 포함된 모든 정수를 더하는 재귀함수를 만듭니다.

from과 to 멤버 또한 범위에 포함되는 것으로 처리합니다.

[입력]

범위 : 5 11

[출력]

합계 : 56

 

typedef struct RANGE

{

int from, to

}R

 

int printSum(int nStart, int nPlus)

{

if(nPlus == -1)

return 0;

else

{

return nStart + printSum(nStart + 1, nPlus-1);

}

}

 

 

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

R r

r.from = 5;

r.to = 11;

int nPlus = r.to - r.from

printf("%d", printSum(r.from, nPlus ));

return a.exec();

}

 

#include <stdio.h>

 

struct RANGE

{

int from, to;

};

 

int Summation( int from, int to );

 

void main()

{

struct RANGE range;

 

printf( "범위 : " );

scanf( "%d %d", &range.from, &range.to );

 

printf( "합계 : %d\n", Summation(range.from, range.to) );

}

 

int Summation( int from, int to )

{

int sum;

 

if( from > to )

return 0;

 

sum = from;

sum += Summation( from+1, to );

 

return sum;

}

 

 

5. 크기가 15인 int 배열을 선언하고, 0부터 11 사이의 난수로 채웁니다.

그리고 배열 내의 위치를 가리키는 정수를 입력받아서,

자신을 포함한 인접한 한 자리 정수들을 모두 -1로 바꾸는 재귀함수를 만듭니다.

[입력]

위치 : 7

[출력]

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

=============================================

3 5 8 10 5 2 0 0 8 3 11 9 1 8 0

3 5 8 10 -1 -1 -1 -1 -1 -1 11 9 1 8 0

 

void replaceFunc(int* nStart, int nSearch)

{

int* number = nStart + nSearch

if ( nSearch != 1)

{

if(*number > 9)

{

return;

}

else

{

if( *number < 10 && nSearch == -1 )

{

*number = -1;

replaceFunc(number, -1 );

}

else if( nSearch != -1 )

replaceFunc(number, -1 );

}

replaceFunc(number, 1 );

}

 

if( nSearch == 1)

{

if(*number > 9)

{

return;

}

else

{

if( *number < 10 && nSearch == 1 )

{

*number = -1;

replaceFunc(number, 1 );

}

else if( nSearch != 1)

replaceFunc(number, 1 );

}

}

}

 

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

int nNumber[15];

int start = 1;

for(int i = 0 ; i < 15; i++)

{

nNumber[i] = rand()%11;

printf("%d = %d \n", i,nNumber[i]);

}

replaceFunc(nNumber, 7);// 위치입력

for(int i = 0 ; i < 15; i++)

{

printf("\n%d = %d \n", i,nNumber[i]);

}

return a.exec();

}

 

 

#include <stdio.h>

#include <stdlib.h>

 

void Init( int array[], int size, int max );

void PrintRuler( int size );

void Print( int array[], int size );

void TurnUp( int array[], int size, int pos );

 

void main()

{

int array[15];

int seed, pos;

 

printf( "씨앗 : " );

scanf( "%d", &seed ); // 15가 적당

 

srand( seed );

 

printf( "위치 : " );

scanf( "%d", &pos );

 

PrintRuler( 15 );

 

Init( array, 15, 12 );

Print( array, 15 );

 

TurnUp( array, 15, pos );

Print( array, 15 );

}

 

void Init( int array[], int size, int max )

{

int i;

for( i = 0; i < size; i++ )

array[i] = rand() % max;

}

 

 

void PrintRuler( int size )

{

int i;

for( i = 0; i < size; i++ )

printf( "%2d ", i );

printf( "\n" );

 

for( i = 0; i < size; i++ )

printf( "===" );

printf( "\n" );

}

 

 

void Print( int array[], int size )

{

int i;

for( i = 0; i < size; i++ )

printf( "%2d ", array[i] );

printf( "\n" );

}

 

 

void TurnUp( int array[], int size, int pos )

{

if( pos < 0 || pos >= size )

return;

 

if( array[pos] < 0 || array[pos] >= 10 )

return;

 

array[pos] = -1;

 

TurnUp( array, size, pos-1 );

TurnUp( array, size, pos+1 );

}

 

 

void replaceFunc(int* nStart, int nSearch)

{

if( nStart[nSearch] < 0 || nStart[nSearch] >= 10)

return

nStart[nSearch] = -1;

replaceFunc(nStart, nSearch-1 );

replaceFunc(nStart, nSearch+1 );

}

 

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

int nNumber[15];

int start = 1;

for(int i = 0 ; i < 15; i++)

{

nNumber[i] = rand()%11;

printf("%d = %d \n", i,nNumber[i]);

}

replaceFunc(nNumber, 5);// 위치입력

for(int i = 0 ; i < 15; i++)

{

printf("%d = %d \n", i,nNumber[i]);

}

return a.exec();

}

 

수정한 코드

 

void replaceFunc(int* nStart, int nSearch)

{

//int* number = nStart + nSearch;

if(nStart[nSearch] > 9 || nStart[nSearch] < 0)

{

return

}

else

{

if( nStart[nSearch] < 10 )

{

nStart[nSearch] = -1;

replaceFunc(nStart, nSearch-1 );

replaceFunc(nStart, nSearch+1 );

}

}

// if( nSearch == 1)

// {

// if(*number > 9)

// {

// return;

// }

// else

// {

// if( *number < 10 && nSearch == 1 )

// {

// *number = -1;

// replaceFunc(number, 1 );

// }

// else if( nSearch != 1)

// replaceFunc(number, 1 );

// }

// }

}

 

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

int nNumber[15];

int start = 1;

for(int i = 0 ; i < 15; i++)

{

nNumber[i] = rand()%11;

printf("%d = %d \n", i,nNumber[i]);

}

replaceFunc(nNumber, 6);// 위치입력

for(int i = 0 ; i < 15; i++)

{

printf("\n%d = %d \n", i,nNumber[i]);

}

return a.exec();

}

'Computer Science > 알고리즘' 카테고리의 다른 글

재귀의 이해  (0) 2017.10.21
알고리즘의 이해(1)  (0) 2017.10.21

인터페이스와 어플리케이션 제작 1에 이은 내용이다

1탄과 마찬가지로 아래 글에서 시작한다

  • (https://www.slipp.net/questions/21 - 어플리케이션 개발은 어떤 순서로 하면 좋을까?)
  • 1탄에서 언급된 컨트롤러 드리븐은 아래의 방식을 취한다
    1. Controller 개발
    2. Business Layer Interface 도출 (Operate Contract)
    3. Business Layer Concreate Class 구현 (Data Access and Biz Layer)
    4. Persistence Layer Interface 도출 (Data Contract)

어플리케이션 개발은 어떤 순서로 하면 좋을까?

이에 대한 윗 글속의 자바지기 님의 순번을 매겨보면 아래와 같다

  1. 중심이 되는 도메인을 설계한다. (Model Class –> business Model)
    • 필수로 쓰이는 개념을 가지고 클래스로 생각하고 연관 클래스 모델과 Package 구조를 잡는다 (모델 설계)
  2. 클래스 구조를 잡은 후 속성들을 하나씩 채워 나간다. (column or property) (모델의 속성 설계)
    • 물리적인 데이터 베이스와 매핑은 하지 않은 상태이기 때문에 기본적인 뼈대와 속성 추가에만 집중하며 전단계의 모델 설계가 개념적 모델링에 가깝다면 지금 단계에서는 논리적 모델링으로 접근한다
  3. 만족할만한 수준으로 뼈대가 완성되면 이때부터 데이터베이스에 대한 매핑 정보를 추가하고 Repository를 구현한다 (DAO or DA의 기초 interface)
    • Repository가 Spring Data JPA를 사용하고 있다면 상관없고 그렇지 않다면 Entity(Model)에 기본적인 CRUD 기능이 가능한 상태로 만들어 준다

그 다음에

  1. Controller를 추가하고 View와 연관지어 사용자 관점의 화면 흐름을 구현하고 테스트한다 (Controller 에서 Business를 구분)

  2. 이 과정을 마친 후에 Business Layer를 하나씩 추가해 Controller와 Repository를 연결하는 방식을 취한다

    • 도메인 모델에 대한 속성이나 구조는 기능을 하나씩 구현해 가면서 계속해서 수정하면서 완성시켜 나간다
      • (위 컨트롤러 드리븐의 2,3,4 번의 일을 하는 것)

여기서

Business Layer의 Interface를 도출하기 가장 좋은 시점은 Controller를 구현한 후 요구사항을 만족하기 위한 Business Layer를 만들어 가능 과정이지 않을까 생각한다는 자바지기 님의 의견이 나온다

이 경우는 실질적인 로직을 Controller 이후부터 보게 된다는 말인 것 같다

처음에는 이런 고민들이 잘 와닫지 않았다

유지보수로만 코딩하다보니 지금까지 정해진 원칙 없이 내가 필요하다고 생각하는 시점에 하나씩 추가하는 방식을 취했기 때문이다

그렇다보니 Interface에 메소드를 하나 추가하는 것을 너무 쉽게 생각하는 것이 아닐까? 라는 의구심이 들었다

"Business Layer에 Interface를 만들어야 할까?" 글에서도 언급되었지만 Interface와 구현 클래스가 1:1 관계라면 Interface를 만들지 않았다

특별히 필요성을 느끼지 못했기 때문이다

그런데 Interface를 만들지 않고 바로 구현 클래스로 넘어가다 보니 Interface의 인자와 반환 값에 대해 신경 쓰는 시간보다 일단 바로 구현을 고민하는 상황이 되었던 것은 아닌가라는 생각도 해본다

즉, Interface를 통한 협업이나 느슨한 의존성 또는 재활용 관점이나 확장에 유연하고 변경에 엄격하게 유지보수를 할 수 있는 방법을 고민하지 못했다는 자아성찰 및 반성이 되는 글이었다

자바지기님 정도의 내공을 가진 사람도 interface에 대한 Input과 Output 보다 내부 구조부터 고민하는 것을 보니 시간이 지나고 실력이 늘어도 더 좋은 방법이라는 고민은 쉽게 풀리지 않는 구나 라는 생각이 들었다

아무래도 정답이 없는 것에 대한 고민은 개발자가 평생 겪는 고민 같은 것이고 만약 나의 답을 내려도 시니어가 되었을 때 내 답만 고집하게 될 수도 있을 것 같다는 생각도 들었다 (개발자도 확장에 열려있어야 한다)

좋은 기준을 정한다는 게 누구나 겪는 쉽지 않은 고민이라는 부분이 눈에 띄었다

댓글의 토론에서 C기반의 헤더파일을 인터페이스에 비유하는 부분이나 그렇기 때문에 헤더파일에 Function을 추가하는 거랑 Interface/Class 에 Method를 추가하는 것에 대한 차이를 논하는 것은 참신하게 놀라웠다

결과적으로는 문법적인 영역으로는 검증이 불가능한부분 이라고 말하고 있지만 개념적으로는 비슷한 부분이 있다고 생각되었다

Object(Class) != Structure + Function 
요즘 OOP에서 강조하는 부분은 Message passing 입니다. 
Method Call을 Function Call 관점에서 보는 것이 아니라, 특정 Object에 Message를 보낸다는 형태로 보는 관점이죠. 
이 관점에서 Interface를 바라보면, 정의되어 있는 Recive Message 에 대한 규칙이에요

이 말을 보면 Object를 가지고 Request / Response를 하는 느낌이다

단순히 Getter와 Setter만 해도 조회와 저장이라는 Message를 보내고 Return을 받기 때문이다

Call / Return 은 Method / Function은 둘 다 가지고 있는 개념이고 Object로서 Message 전달이나 내부 로직에 의한 데이터 가공이 Request / Response 의 역할로 추가된 은유이다

여기에 하나의 전제조건이 있다

Interface 설계를 (좀 더 이상적으로) 하려면 설계자는 필히 외부 API 설계 경험이 있어야 한다

"Facebook API 고쳐주세요!"하면 "있는 걸로 하세요. 다음 버전에는 고려해 볼께요." 라는 이야기가 나오는게 정상이지

 "고객님 죄송합니다. 바로 고쳐 드릴께요."라고 할 리가 없다고 봅니다

이 부분이 너무 찔리고 있다….

초기 개발 수준에는 Interface의 역할에 대한 이해 조차 없다고 봐야겠다

그냥 비슷한데 결과만 다른 메소드 하나 추가하면 저런 요구사항은 구현으로는 누구나 할 수 있다

하지만 변화가 발생했을 때

어디까지 영향이 가는지 그리고 이런 예측이 힘들면 새로 만드는 명함만 개발자들의 특성상 1:1 매칭되는 클래스나 인터페이스나 메소드나 이런류의 결과가 계속 나오게 되는 것이다

이런 부분의 1:1로 개발하는 이유가 전체 구조에 대한 고민은 적게하고 당장 요구한 부분만 고민을 하면서 개발이라는 흉내를 내기 때문이다

요구사항만 들어주면 되고 내부 구조는 갉아먹고 있었던 것이다……

근데 진짜 고민했는데도 기존 구조상에서 좌절해서 포기해서 어쩔 수 없이 1번 2번 숫자 네이밍 달린 매소드를 만들 때 (전체적으로 바꿔야 하는 10년이상의 시스템에서) 이를 방지하려면 처음부터 고민해서 잘 만들어 나가야 한다

일단 만들고 나중에 리팩토링도 가능하겠지만 이는 능력자들의 고려대상 이고 보통의 개발자라면 충분히 고민하고 품질관리가 무었인지 알고 품질이라는 것은 제조업의 QC, QA가 아니라 SW에서 충분히 보증서가 된다는 것을 몸소 느끼는게 성장하는 방향이 맞다고 본다(어디가 좋은 회사이지? 이런 것을 결정할 수 있는 이유로도)

결론이 기술적인 탐구보다는 반성이 되었는데 이게 과거에 썼던 글이고 처음으로 SW 개발을 어떻게 해야 좋은 방법이지? 라는 의구심의 시작이었던 것 같다

그래도 고쳐쓰면서 다시 한번 새롭게 시작할 수 있었던 것 같다

참고된 글을 몇개 더 남기자면

위 연관 시리즈에서 맨 처음 보게된 글이었고 여기서 든 생각은 계층과 계층간에 data를 주고 받을 때 DTO를 사용할 것이며 그 DTO안에는 DB로부터 얻은 결과 or Business Model 들을 가지고 있다 라는 생각이다

그리고 지금은 DTO를 여러개 만들기 보다는 DA나 Biz에서 사용하는 Model들을 내부 Method를 통해 Builder 형태나 Static Method를 통해 생성시키고 모델 내부에서 비즈니스 변경을 처리하는 구조로도 개발 해보고 있다

마지막으로 고민해봐야 하는 내용이 있다

Java를 만든사람들이 보기에, Object-oriented Programming 은 "How" 보다는 "What"에 초점을 맞추고 있다고 합니다. 여기서 대명사처럼 쓰인 Java는 OOP라는 개념으로 생각됩니다.
전체 개발 과정에서 "What(Domain)"에 관심을 가지면, Interface 설계는 자동으로 따라오는 거라고 봅니다. 
결론은 정상적인 Domain 설계는 Interface 설계를 포함하고 있다고 한다 (1탄의 명사와 동사의 관계와 비슷한 느낌이 있다) 
Method 들의 Set(집합) 으로 Domain이 겪어야 할 Method를 같이 설계에 넣어야 하는 것이라면 CRUD도 Interface 설계의 일부분이다  

이제 최종적으로 내가 이 글을 쓰면서 생각하게 된 Application(why) 개발에 대해 남긴다

글쓰기는 그렇지 않지만 프로그래밍에서는 항상 How(구현이)가 생각난다

How를 생각하면 일정도 산정할 수 있을 것 같고 문제를 푸는 기분으로 뭔가 일을 해결할 것 같다

이 당시 Spring을 사용하지 않기에 .Net에 기반에서는 IoC Container는 객체를 생성하고 삭제하는 것을 쉽게 “대신” 해주는 라이브러리로 사용하는 녀석이고

DI Patten도 Interface를 사용했기 때문에 생성과 사용에서 느슨한 결합상태가 되었고(특정 하나의 class로 강제하지 않기 때문) 이 인터페이스를 사용하는 객체가 올 거라고 의존성 주입이라는 어려운 말을 써서 “미리” setting 된 Objcet를 적시적소에 사용 할 수 있는 부분들이었다

위 내용을 보면 Why 이런걸 사용해야 하지?? 라는 생각보다 이걸 어떻게 구현하거나 사용해야 하지? 와 같은 How에 대한 생각들이 먼저 되었다

달라져 보기로 했으니 보편적인 언어로 다시 고쳐써 본다면

여기서 why를 먼저 생각한다는 것은 개발자가 현재 Coding 하는 것이 과거와 비교해서 더 편해 보려고(레거시 프레임워크) 대신해주는 녀석을 새로 만들게 되었고

유지보수 할 때 Interface를 사용해서 변경에 종속되는 것이 아니라 느슨하게 결합되어

관심사에 따라 Interface가 분리되어 업무에 따른 다른 사람의 수정사항에 대해 영향을 받지 않고 다른 개발자와 Interface로도 협업이 가능하게 하는 구조로 개발하는 것이 되었다

즉, 구현에 전전 긍긍하고 쫄리기 보다는 어떻게 하면 개발자 편하게 뭔가 생성해 내고 여러 명이 같이 유지보수 할 수 있는 구조를 정할 수 있지??

단순한 규칙을 정해서 복잡하지 않고 누구나 쉽게 개발에 접근할 수 있는 것을 어덯게 만들지??

이런 접근 방법이라면

  1. Application 개발도 결국에는 개발자가 편한 구조가 되야 하고

  2. 변경에 대한 영향도가 없고 확장이 쉽게 개발하는 접근이 되야 하며

  3. 새로운 것을 도입하거나 기존방법에서 변경사항이 생길 때에도 이걸 왜 해? 질문을 하고 대답할 수 있어야 한다

이 결과물과 고민이 더 편한 Application 개발을 할 수 있는 것이라면

한 문장의 결론 : Interface를 통해 OOP 개발을 하는 것은 이것을 이용하는 것이 
라이브러리의 활용이나 개발 방법에서 기존의 개발방법 보다 더 편하고 생산적인 Application 구조가 되기 때문에 
더 좋은 SW 만드는 것이 가능하기 때문에 잘 활용할 수 있어야 한다    

하지만 정답은 없다고 본다. 다만 지금의 생각이 나중에 새로운 걸 접했을 때 머물지 말고 더 발전되길 바란다

'Computer Science > 소프트웨어 공학' 카테고리의 다른 글

Interface와 Application 제작 1  (0) 2021.02.22
Domain Model vs DTO  (0) 2019.02.28

초안은 2018년도에 내 마음대로 작성한 글인데 그때보다 지금의 지식으로 정제하기 위해 고쳐쓰기로 마음먹었다

이 글 본문을 읽기 전에 먼저 VO vs DTO vs DAO vs Domain Model 을 구분할 수 있는지 가슴에 손을 얻고 생각해 봐야할 것 같다

이 글을 적으면서 Fix 하려고 하는 개념이지만 이게 진짜 맞을까? 라는 의심 한구석이 생겨나고 있는데 일단 너무 개념과 용어에 매몰되어 바보가 되진 말자

먼저 객체에 대해 개념 그룹핑을 한다

  1. Data Object

    1. VO
    2. DTO
  2. Entity Object

    1. Domain
    2. Database Table
  3. Imutable Object

    1. VO
  4. Data Access Object

    1. DAO

크게 4가지로 구분해보았다 그리고 중요한 점은 위의 4가지 모델을 동일선상에 두고 비교하지 않는다.

동등비교할 대상이라기 보다는 구분해야하는 대상으로 보이기 때문에 개념적인 분기로서 모델을 구분한다. 즉 귀에 걸면 귀걸이 코에 걸면 코걸이 같은 녀석들의 개념 의존성을 구분하고자 한다

서론이기 때문에 가볍게 용어정리를 하면

  • VO(Value Object): 값을 생성하면 그 값이 변하지 않을 객체(Read Only)
  • DTO(Data Transfer Object): 계층과 계층간의 데이터 전송을 목적으로 생성하는 객체
    • 서버 Side : Database data -> Business Layer -> DTO -> Presentation Layer -> API response data(JSON/XML) -> Clinet
    • 클라이언트 side : API request data(JSON/XML) -> Presentain Layer -> Business Layer -> DTO -> Data Access Layer -> Database System
  • DAO(Data Access Object): 실제로 DA에 접근하는 Layer 에 있는 객체로 data를 가공하는 업무 비즈니스 로직과 구분된다. 즉 database의 data를 조회하거나 조작하는 기능만을 전담하도록 만든 객체
  • Domain: 시스템에서 다루려는 대상 그 자체로서 가볍게 설명하자면 database의 Entity와 비교된다 하지만 Table 적으로 보는 DB 관점이 아닌 Object-Class 관점에서 바라보는 객체지향적 객체라고 정의한다

위 내용은 DDD 챕터를 만든다면 추가 적으로 얘기하겠다

본론으로 돌아와서 제목에 관련된 토론을 시작하게 된 글과 함께 시작해보자

흥미로운 부분은 비즈니스 레이어에 외부와 협업이 있으면 Interface가 필요하다는 내용이 있다

아무것도 모르고 기존 MVC 패턴의 코드만 보고 흉내내면 비즈니스의 구현체 마다 Interface를 매번 만드는 노가다성 작업을 맞닥들일 때가 있다

실제 구현 클래스와 1:1 관계로만 사용되는데 이렇게만 사용하면 의미가 있냐는 것이다

의미있게 사용하는 방법에 대해서 필자는 기능적 역할에 대해 어느 정도 세밀한 분리(일단 추측)를 가진 Interface로 나누어서 기능별로 관리하고 필요한 인터페이스를 상속받아 구현하는 것에 대해서 의미 있다고 생각하고 있다

Layer 별로 개발자가 다른 경우(시스템이 달라질 정도여야 가능한 시나리오??)도 멀티로(병렬로) 다양한 구현 클래스들을 생성해야 하는 경우도 필요하다고 생각하고 있다

복잡도에 대한 내용이 있는데 비즈니스적 업무가 복잡하다면 추상화를 통해 Interface를 분리하는 것에 대해도 긍정적으로 생각하고 있다

이런 상황에서 Interface는 공통된 관심사의 분리의 구현체로 생각된다

설계 또는 기획과 같은 부분에서 분리된 내용을 바탕으로 Interface 에서 쪼개진다는 것이다

그렇기 때문에 추상화 라는 개념 속에서 공통된 관심사를 Interface로 분리한 것이다 라는 생각이 드는 글이었고 우리는 개념적으로 공통점과 차이점을 추출 할 수 있어야 이와 같은 기반의 설계를 한다는 생각도 들었다

추상화 -> Interface -> 구현 클래스

이런 식의 규칙도 관심사를 분리하고 로직을 분리하고 필요 의존성에 따라 응집도를 높이고 결합도를 낮추는 행위로서 Interface 생성과 분리를 이해해야 한다고 얘기하고 싶다

위 글의 토론 중에 반복적으로 Interface –> 구현 class의 방법을 지양해야 한다는 말이 나오는데 실제 내가 했던 Ludy라는 플랫폼 개발 업무에서 GameContents 쪽은 추상화의 대상이 되는 Model이 간단하고 그것에 CRUD에 대한 로직이 간단하기 때문에 습관적 사용으로도 구현을 하는데 문제가 없었다

다만 이런 부분에 대해 고민해 보지 못한다면 원래 그렇게 Interface를 항상 만드는 것인 줄 알았을 것도 같다 (멍청이가 되지 말아야 하는 부분)

IoC Container에서 Interface를 사용해서 객체를 생성하는 부분이 있기 때문에 개발실 위키에 있는 계약기반 이런 내용과 관련해서 만드는 것도 객체 생성의 응집력을 높이려면 제대로 만들어진 Interface를 주입해야 한다는 생각도 하게 되었다

즉 Interface를 만드는 것은 이유가 있어서 만드는 것이지 폼으로 만드는 것이 아니라는 것이다

그리고 유연함과 확장성 이라고 썼는데 공통된 것을 모으고 그것들을 차이점들을 분리해서 처리할 수 있는 부분이라고 생각하면 말로는 쉬운 것 같다

인터페이스를 우선 적으로 고민하는 부분도 나중에 유지보수 할 때 확장성에 대한 보험이 될 수 있다는 부분도 공감이 된다

다만 너무 디자인 패턴적으로 매몰되었는지 이 당시 파트장이 내가 Interface를 처음 사용하면서 나도 모르게 불필요한 미래의 확장을 생각해서 뭐든지 Interface로 객체들을 생성하면 단순히 Class로 바꿔주었던 부분도 있었다(오버 엔지니어링은 역효과가 날 수 있다는 생각도 든다)

1:1로 될 것 같으면 지양하지만 뼈와 살의 예를 들어서 뼈의 역할을 인터페이스로 표현하고 살을 붙인다는 표현도 재미있는 표현이었고 Interface로 계약서를 작성하고 계약서대로 개발한다 이런 내용도 쉽게 이해하려는 노력인 것 같다

토론에서 게임 분야 개발자의 1:1 매칭에 대한 반성과 우연히 2개의 구현체에 대한 수정을 하게 되면서 class 상속이 아닌 Interface 상속으로 변하는 것과 공통되는 것을 구분하게 된 것에 대한 장점을 겪어본 것은 개발자가 성장하면서 부딪칠 문제와 해결법들이 공감이 되는 부분도 있다

토론의 결론은 Interface 우선 설계가 중요하다로 끝난다 그리고 막판쯤에

개발 방식은 Controller 개발 => Business Layer Interface 도출 => Business Layer Class 구현 => Persistence Layer Interface 도출

이런 형태로 진행되어야 하는데 이 같은 접근 방식이 괜찮다고 생각하냐? 그렇다면 도메인 설계는 어느 시점에 하는 것이 좋을까?

애플리케이션을 개발할 때의 개발 순서에 대해 고민해 봐야겠다 라는 말을 하게 된다 (2탄에서 다룰 내용!)

위 질문에 대해 다시 한번 질문을 하는데 컨트롤러 드리븐 개발은 도메인 설계에서 객체를 조합하거나 컨트롤 할 수 있는 레이어부터 해결해 나가는 방식이 아닐까?

Ludy(플랫폼)에서 Service 층을 나누게 되는데 인게임 상점으로서 처리가 필요한지 웹 상점으로서 처리가 필요한지 생각해야 하는 부분에서 공감이 되었다

즉 분기의 시작이 Controller라면 다른 각각의 Service를 호출할지 혹은 Controller 까지 InGameShopController of WebShopController 분리할지 비즈니스 적으로 확장과 생성을 구분해야 하는 부분으로 이해되었다

그리고 제일 중요한 개념 같은데 인터페이스 설계는 행위의 설계이고 도메인 설계는 실제 업무의 설계/라는 말이 나온다

나는 이게 명사와 동사 같다

도메인 설계(DB Table이라면 Entity라는 것으로)는 명사적인 것으로 상품, 포인트, 구매 환전 같은 명사적인 설계이고

행위의 설계는 상품을 조회하다 / 포인트를 수정하다 / 웹상점으로 구매하다 / 인게임상점으로 구매하다 / 포인트를 게임머니로 환전하다 / 실제 돈을 게임머니로 환전하다 등등 명사를 활용하는 부분을 만드는 것으로 생각이 된다

이런 생각으로 인터페이스 우선 설계를 하게 된다면

ERD 같은 것을 그리기 전에 마인드맵을 먼저 그려서 도메인을 중심으로 가지 치기 하면서 뻗어나간 행위들을 이어 나가면

필요한 컬럼이 무엇인지 그리고 필요한 if나 switch case가 어떤 상황이 있는지 또는 다른 도메인과 결합 될 것이 무엇인지 그림으로 그리고 나서

테이블의 컬럼이나 Interface에 사용될 Method들을 수집하는 것이 비즈니스의 복잡도를 낮춰가면서 개발하는 방법으로 생각된다

말로 설명한다는 것이 무척이나 어려운데 구현과 일상 세계간 차이야 당연히 있을 것이다

핵심은 구현에만 치중하지말고 확장에 유연하고 변경에 엄격한 시스템 구조가 되어야 품질관리와 품질보증이 되는 시스템의 시작이라는 생각으로 1탄을 마무리 한다

'Computer Science > 소프트웨어 공학' 카테고리의 다른 글

Interface와 Application 제작 2  (0) 2021.02.22
Domain Model vs DTO  (0) 2019.02.28

협업하는 개발자의 필수역량 중에 하나가 Git을 잘 사용하는 것인 세상이 되었다

Git을 쓰지 않는 회사를 찾기 힘들게 된 것 같다. 물론 모든 회사가 Git 을 쓰지는 않겠지만 적어도 Git 보다 좋은 평을 듣는 Source Vesion Control 프로그램은 듣지 못한 것 같다 

그리고 협업 툴이라는 것은 기본적으로 무엇이 되었든 공통 규칙 즉 Convension 이라는 것이 필요하다. 협업을 한다는 것은 의사소통비용이 소비되기 때문에 사람과 사람간에 의사소통을 잘하는 것으로도 일을 잘한다는 말을 들을 수 있다고 본다 

서두는 여기까지 하고 어떻게 하면 Good Commit Message 를 작성할 수 있는지 그리고 왜 Good Message 를 쓸 줄 알아야 하는지 그 이유와 결론을 내보자 

Good Commit Message를 작성하는 방법이라는 물음으로 아래 블로그글을 참고 해봤다 

 

좋은 git 커밋 메시지를 작성하기 위한 7가지 약속 : TOAST Meetup

git커밋

meetup.toast.com

 

ull.im

울려 퍼지다.
반향하다.
공명하다.

blog.ull.im

두 블로그 모두 공통의 영문 블로그를 참고하고 있다 (https://chris.beams.io/posts/git-commit/

일단 고려사항을 정리하면 

  1. 언어적 특성 
    1. 영어 or 한글 
  2. 문장의 구성
    1. 글을 남기는 방법 : 자세하게가 아닌 간결하게 작성하는 것은 한영 구분이 없다 (오픈소스는 전세계에서 사용하는 도구) 
    2. 기본적으로 형태 : 명령문의 형태를 가지고 있다 

개발자가 왜 글쓰기를 고민해야 하지? 라는 물음을 한다면 일단 좋은 Git Commit을 써야 하는 이유부터 생각해 보자 

  • Commit Log 의 가독성을 위해서 
  • 더 나은 협업과 리뷰 프로세스를 가진 팀이 되기 위해서 
  • 더 쉬운 코드 유지보수를 하기 위해 히스토리 관리를 하기 위해서 

이런저런 이유를 나열 하면 끝이 없을 것 같고 크게 가독성 / 조직 화합 / 유지보수성 이 3가지를 생각했다

즉, 내가 한 일을 잘 나타내기 위해 작성하는 짧은 글쓰기로서 필요하다고 주장한다 

위 블로그의 내용의 기반이 영어로 되어있고 내용을 읽어보면 내가 딱히 수정을 하거나 더 요약할 필요성을 느끼지 못해 그렇다면 영어가 아닌 한글로 Commit Log를 남기려면 어떻게 해야할까? 라는 질문으로 수정해본다

먼저 좋은 Git Commit Messge를 위한 보편적인 노력으로 나열해 보면 

  1. 제목과 본문을 한 줄 띄워 분리하기
  2. 제목은 글 기준 25자 이내로 
  3. 제목 끝에 . 금지 
  4. 제목은 명령조로 
  5. 본문은 36자 마다 줄 바꾸기 
  6. 본문은 어떻게 보다 무엇을, 왜에 맞춰 작성하기 

1. 제목과 본문을 한 줄 띄워 분리하기 

  • 꿀팁과 같은 내용으로 제목 + 빈줄 + 설명문으로 구성하면 log 내용을 한줄로 보기로 출력하면 제목만 볼 수 있어서 좋다. 만약 줄 바꿈이 되지 않으면 설명문까지 모두 한줄에 포함되어 나오기 때문에 가독성이 저하된다
  • git log --oneline / git shortlog 
    • 이 명령어로 보면 가독성 차이를 알 수 있다 

2. 제목은 글 기준 25자 이내로 

  • 영문기준 50자로 가이드 되어 있어 한글 기준으로 절반으로 줄이면 좋을 것 같다 

3. 제목 끝에 . 금지 

  • 문법적인 접근으로 제목에는 보통 점을 찍지 않는다 

4. 제목은 명령조로 

  • 영어에서 가져와서 첫 단어를 동사원형으로 쓰기 위함이다 
  • 명령문보다는 설명문의 느낌으로 하자 
    • "인증 메소드 고쳐라" 가 아닌 "인증 메소드 고쳤다" 의 느낌 
    • "인증 메소드 수정" 으로 한글에서는 문장보다 구문이 낫다 

5. 본문은 36자 마다 줄 바꾸기

  • git 은 자동으로 메시지 내용은 줄 바꿈을 해주지 않는다 
  • 영문기준 72자 이기 때문에 한글로는 절반인 36자로 잡았다 
  • 가독성을 위해 끊이 쓰기를 하는 것이다 

6. 본문은 어떻게 보다 무엇을, 왜에 맞춰 작성하기 

  • 이 부분은 내용 전달에 대한 부분이라 글쓰기를 하는 방법으로 보인다 
  • 무엇을? 왜? 와 같은 부분은 결론을에 대한 접근 방법이고 어떻게? 는 과정에 대한 접근이라면 간결한 의미 전달을 위해 결론 부터 쓰는 접근으로 이해된다 

추가로 영어일 경우 문법적으로 아래를 권장한다고 한다 

  1. 동명사 보다는 명사를 사용
    1. ing 를 붙이기 보다는 순수 명사 그대로 쓰기 
  2. 부정문 Don't 를 사용하기 
    1. 명령조 어투에서 반대인 부정 명령구를 써서 부연 설명없이 간단하게 부정하기 
  3. 오타 수정과 같은 경우 correct miss spelled 가 아닌 그냥 Fix typo로 쓰기 
    1. 사소한 것에 대해 구구절절 설명할 필요는 없다

이제 좋은 Commit Message를 위한 영어 동사를 한글로 변환한다면 어떻게 접근하고 사용해야 될까? 

일단 영어 방법의 경우 위의 블로그 설명대로 동사로 시작하는 convention을 적용해도 전혀 손색이 없어 보인다. 한글일 때도 한번 동일하게 접근해보자 

ex) Fix 를 사용하기 - 이 경우 올바르지 않은 동작을 고친 경우에 사용하게 된다 

  1. Fix 회원 가입 
  2. 고침 회원가입
  3. 수정 회원 가입 
  4. 회원 가입 수정  

위의 예시는 Fix를 나타내고 싶은데 한글과 어울림이 애매한 어투가 느껴진다 

일단 4번의 경우가 가장 일반적인 한글 사용법으로 보인다. 즉 명령조로 시작하니 한글 구조에서 어색함이 느껴진다. 그렇다고 평서문의 느낌으로 단순한 구문인 회원 가입 수정이라고 목적어 + 서술어로 표현하니 이 또한 동사원형 표현법과 비교해 어색함이 느껴진다

영어의 동서원형이 제목과 같은 키워드 성격이 있어서 처음부터 눈에 들어오는 부분이 있어 생각의 연결성을 이어주었으나 한글은 실제 행위의 서술어가 마지막에 있어 문장 전체를 보아야 정확히 의미 전달이 종료된다 

영어의 동사원형을 말머리에 썼을 때 어떻게 눈에 들어오는 지 나열해보고 그 통일성 있는 규칙에서 차이를 발견해 보자 

Make config object read-only
make 'floating patch' message informational
Make values optional in ViewPropTypes
make read() be called indefinitely if the user wants so
make IsolateData store ArrayBufferAllocator

이런 형태를 보면 차이가 느껴지는가? 실제 내용을 떠나서 막연히 뭔가 만들었구나 하고 생각이 시작 된다. 조금만 더 명분을 찾아 보자 

언어적인 Convention을 만드려는 이유는 무엇인가? 

주관적인 생각이지만 의사소통 비용을 줄이기 위함이라고 생각한다. 사람간의 대화에 있어 의사소통 비용이라는 것은 때로는 피로감을 만들 수 있다.

즉, 이번에 이 얘기를 했으니 다음에 또 반복해서 하지 말자식의 약속을 암묵적으로 하게되면 적어도 까먹지 않게 일정한 패턴을 가지거나 암기하기 쉬운 구조가 있어야 한다 (방정식의 풀이를 가르쳐 줄 때 "근의 공식"을 써라 이런 대화도 비슷한 이치) 

아래와 같은 형태는 어떠한가? 

[수정] 회원 가입의 버그를 수정

명령문처럼 어떤 작업이었는지 한 눈에 읽히고자 했고 어설픈 영어 번역을 할 필요없이 국어적인 주어 / 목적어 / 서술어로 제목 키워드를 보충 설명이 되었다고 보이는가? 

동사원형이 아닌 명사로 행위의 목적이 무엇인지 표현했다

간단하면서 한 줄로 명확하게 의미 전달을 하고자 한다면 그리고 제목과 본문 중에 더 중요한 내용이 제목이라면 블로그의 글쓴이가 작문이 아니라 패턴으로 접근해야 한다는 말이 공감이 되었다 

 "결국 커밋 로그 메시지의 작성은 작문이 아니라 패턴으로 접근해야 합니다. 자신의 커밋이 가진 특징을 패턴에 대입시켜 단어들을 뽑아내는 것이죠. 작문의 결과로 보면 너무 단순해서 부족해 보일지 몰라도, 여러 사람들에게 쉽게 읽히고 쉽게 이해되도록 하기에는 패턴화된 단순한 문장이 훨씬 낫습니다"

출처: <https://blog.ull.im/engineering/2019/03/10/logs-on-git.html>

작성한 당사자가 아닌 제 3자가 이해할 수 있는 문장을 만드는 것 자체가 쉬운일이 아닌 것이고 그것이 필요한 이유는 의사소통 비용을 줄이기 위함이라면 최선의 단어를 쓰는 것과 최선의 문장 혹은 문단을 고민해서 남에게 보여주는 부분은 코딩을 잘해서 보여주기 위함과 비슷한 이치를 가진다 

좋은 Message를 작성해서 내가 한 일에 대해 의미 전달을 잘 하는 것으로 내가 작성한 변수 혹은 함수나 인터페이스를 통해 더 발전된 협업과 대화를 하고자 함이기 때문에 같이 프로그래밍 일을 하는 사람의 기본 소양으로도 부합된다  

다만 차이가 있다면 컴퓨터 언어적 이쁜 코드를 작성하는 것이 아니라 함축적이지만 이해하기 쉬운 패턴으로 의미를 전달하는 방법이 중요한 차이점이라면 그 차이가 이 글의 질문이었던 Good Commit Message 에 대한 답이 되었을까?

질문 : 좋은 커밋 메시지는 무엇인가요? 
대답 : 함축적이지만 이해하기 쉬운 패턴으로 만들어진 문장 or 문단 

마지막으로 한글과 영어로 구분한 예시 표로 마무리한다

아래의 내용을 보면 중복되는 의미의 단어들도 많이 보인다. 해당 부분은 센스있게 자주 쓰이는 단어를 쓰면 되겠다

단어

한글

영어

Fix

[고침] 통계 캐시 버그를 수정

[고침] 로그 항목을 변경

[고침] 깨진 검색 경로를 고침

Fix stat cache

Fix changelog entry

Fix broken jsiexecutor search path.

보통 올바르지 않은 동작을 고친 경우에 사용합니다.

Add

[추가] 에러 내용을 추가

[추가] ListView 미사용 문구를 추가

Add error description to Image onError callback

Add displayName to ActivityIndicator

Add deprecation notice to SwipeableListView

코드나 테스트, 예제, 문서 등의 추가가 있을 때 사용합니다

Remove

[삭제] 불필요한 문구 삭제

[삭제] 사용하지 않는 파일 삭제

Remove unnecessary italics from child_process.md

Remove useless additionnal blur call

Remove unneeded .gitignore entries

Remove unused variable

Remove duplicated buffer negative allocation test

코드의 삭제가 있을 사용합니다. Clean’이나 Eliminate’를 사용하기도 합니다. 보통 A 앞에 unnecessary, useless, unneeded, unused, duplicated’가 붙는 경우가 많습니다.

Use

[사용] message 전달에 가짜 Event 사용

[사용] error 내용 전달에 객체 writer 사용

Use fake MessageEvent for port.onmessage

Use object writer for thrown errors

Use ru_stime for system CPU calculation

Use relative path for SCRIPTDIR

특별히 무언가를 사용해 구현을 하는 경우입니다.

Refactor

[개선] 쓰레드 관리 체게를 개선

Refactor tick objects prune function

Refactor thread life cycle management

Refactor QueryWrap lifetime management

Refactor argument validation

Refactor thread life cycle management

Refactor MockNativeMethods in Jest

전면 수정이 있을 때 사용합니다.

Simplify

[단순화] 쓰지 않는 검사를 단순화 시킴

[단순화] GetCPUInfo 반복을 개선

Simplify code and remove obsolete checks

Simplify the setup of async hooks trace events

Simplify heap space iteration

Simplify TriggerNodeReport()

Simplify AliasedBuffer lifetime management

Simplify loop arithmetic in GetCPUInfo

복잡한 코드를 단순화 할 때 사용합니다. Refactor의 성격이 강하나 이보다는 약한 수정의 경우 이용하면 좋습니U.

Update

[수정] acorn 버전을 수정

Update acorn to 6.1.0

개정이나 버전 업데이트가 있을 때 사용합니다. Fix와는 달리 Update는 잘못된 것을 바로잡는 것이 아니라는 점에 주의해야 합니다. 원래도 정상적으로 동작하고 있었지만, 수정, 추가, 보완을 한다는 개념입니다. 코드보다는 주로 문서나 리소스, 라이브러리등에 사용합니다.

Improve

[향상] http/1 호환성을 향상

Improve compatibility with http/1

Improve Unicode handling

Improve test coverage in perf_hooks

Improve validation of report output

Improve performance of test-crypto-timing-safe-equal-benchmarks

Improve color detection

Improve Android Network Security config

Improve Accessibility

Improve iOS's accessibilityLabel performance by up to 20%

향상이 있을 때 사용합니다. 호환성, 테스트 커버리지, 성능, 검증 기능, 접근성 등 다양한 것들이 목적이 될 수 있습니다.

Make

[변경] 읽기 전용 설정의 속성을 변경

Make config object read-only

make 'floating patch' message informational

Make values optional in ViewPropTypes

make read() be called indefinitely if the user wants so

make IsolateData store ArrayBufferAllocator

주로 기존 동작의 변경을 명시합니다.

새롭게 뭔가를 만들었을 때는 Make 대신, Add 사용해야 합니다.

Implement

[향상] 캐시를 사용해서 get data 속도를 향상

Implement requiresMainQueueSetup in RCTTVNavigationEventEmitter to satisfy Xcode warning

Implement an in-memory cache store to save parsed and validated documents and provide performance benefits for repeat executions of the same document

코드가 추가된 정도보다 더 주목할 만한 구현체를 완성시켰을 때 사용합니다.

Revice

[문서수정] Readme.md 파일을 수정

Revise deprecation semverness info in Collaborator Guide

Update와 비슷하나 문서의 개정이 있을 때 주로 사용합니다.

Correct

[타입변경] 회원 Id 리턴 타입을 문자열에서 정수로 변경

Correct grammatical error in BUILDING.md

Correct parameters, return types in crypto.md

Correct styling of _GitHub_ in onboarding doc

Correct buffer changelog ordering

Correct async_hooks resource names

주로 문법의 오류나 타입의 변경, 이름 변경 등에 사용합니다.

Ensure

[성능보장] 탐색 알고리즘의 복잡도를 nlogn 보장  

[기능보장] 에러를 던졌을 알람발생을 보장

Ensure quiet always takes precedence

Ensure cookies with illegal characters are not sent to okhttp

Ensure require.main for CJS top-level loads

Ensure Stream.pipeline re-throws errors without callback

Ensure options.flag defaults to 'r' in readFile

무엇이 확실하게 보장받는다는 것을 명시합니다. if 구문처럼 조건을 확실하게 주었을 때에도 사용 될 수 있습니다. ‘Make sure’도 같은 용도로 사용될 수 있습니다.

Prevent

[방지] 회원 검색 로직에서 무한루프를 방지

Prevent multiple connection errors

Prevent constructing console methods

Prevent event loop blocking

Prevent a potential error in event handling if Object.prototype is extended.

Prevent an infinite loop when attempting to render portals with SSR.

특정한 처리를 못하게 막습니다

Avoid

[회피] 불필요한 밸리데이션 체크를 회피

Avoid flusing uninitialized traces

Avoid overrun on UCS-2 string write

Avoid race condition in OnHeaderCallback

Avoid memory leak on gc observer

Avoid materializing ArrayBuffer for creation

‘Prevent’는 못하게 막지만, ‘Avoid’는 회피합니다. if 구문으로 특정한 동작을 제외시키는 경우에도 사용 할 수 있습니다.

Move

[이동] Member Package 위치를 Account 이동

Move test-process-uptime to parallel

Move function from header to source file

Move async hooks trace events setup to pre_execution.js

move initialization of node-report into pre_execution.js

코드의 이동이 있을 때 사용합니다.

Rename

[이름변경] Member 클래스 이름을 Members 복수로 변경

Rename node-report to report

Rename location to trigger

Rename node-report suite to report

이름 변경이 있을 때 사용합니다.

Allow

[허용] 서비스 레이어의 로깅을 필수로 하도록 허용

Allow the output filename to be a {Function}

Allow Node.js-like runtimes to identify as Node.js as well.

Allow passing parseOptions to ApolloServerBase constructor.

Allow an optional function to resolve the rootValue, passing the DocumentNode AST to determine the value.

Make와 비슷하지만, 허용을 표현할 때 사용합니다.

Verify

[검증추가] 메모리 누수 방지를 위한 방어코드를 추가

Verify heap buffer allocations occur

검증 코드를 넣을 때 주로 사용합니다.

Set

[속성변경] 회원 가입 상태 확인 배치 스케줄러의 시간을 12시간으로 변경

Set tls.DEFAULT_ECDH_CURVE to 'auto'

변수 값을 변경하는 등의 작은 수정에 주로 사용합니다.

Pass

[전달] 회원가입 API 요청값을 응답값에도 재활용하도록 전달

Pass the response toolkit to the context function.

파라메터를 넘기는 처리에 주로 사용합니다.

'Developer > Git' 카테고리의 다른 글

Git Repository 등록방법  (0) 2018.07.15

읽은 책


  1. 프로그래밍 언어의 이해 - [김일민, 김성동] "홍릉과학출판사" 

  2. 인터넷 이해와 활용 - [안치현, 김형철] "한빛미디어" 

  3. OS가 보이는 그림책 - [ANK Co., Ltd / 이영란] "성안당" 

  4. 비전공자를 위한 이해할 수 있는 IT 지식 - [최원영] "티더블유아이지"

  5. 좋은 코딩 나쁜 코딩 - [박진수] "한빛미디어"

  6. 프로그래밍 수련법 - [브라이언 W 커니핸, 롭 파이크 / 김정민, 장혜식, 신성국] "인사이트"

  7. 좋은 코드를 작성하는 기술 - [아가타 토시타카 / 정인식] "제이펍" (추천)

  8. Head First SQL - [린 베일리 / 박종걸] "한빛미디어" (추천)

  9. 인프라 엔지니어의 교과서 - [사노 유타카 / 김성재] "길벗" (추천)

  10. C# 코딩의 기술 기본편 - [가와마타 아키라 / 김완섭] "길벗"

  11. 뇌를 자극하는 C# 4.0 프로그래밍 - [박상현] "한빛미디어"

  12. 점프 투 파이썬 - [박응용] "이지스퍼블리싱" 

  13. 그림으로 배우는 알고리즘 - [스기우라 켄 / 서재원] "영진닷컴" (추천)

  14. 그림으로 배우는 HTTP Network Basic - [우에노 센 / 이병억] "영진닷컴" (추천)

  15. 하루 3분 네트워크 교실 - [아미노 에이지 / 김현주] "영진닷컴"

  16. 얇지만 얇지 않은 TCP/IP 소켓프로그래밍 C - [마이클 도나후, 케네스 칼버트 / 유재필] "Bj퍼블릭"

  17. Effective C# - [빌 와그너 / 김명신] "한빛미디어"

  18. More Effective C# - [빌 와그너 / 김완섭] "한빛미디어"

  19. 개발자가 되고 싶으세요? - [이상민] "로드북"

  20. 프로그래머가 몰랐던 멀티코어 CPU 이야기 - [김민장] "한빛미디어" (추천)

'My Reading > Book' 카테고리의 다른 글

2021 내가 읽은 + 읽을 개발서적 정리  (0) 2021.01.23

진유림/한정수 이 두분의 세미나를 우연히? 듣게 되고 공부를 어떻게 해야 하는가에 대한 깊은 고민을 했던 것 같다.

사실 난 공부를 못한다. 정확히는 자가학습이라고 해야하나 정해진 루트와 패턴 혹은 공식 찾기와 같은 문제 해결 말고는 직접 분석하고 혼자서 차려먹는 공부를 어렸을 때부터 못했다. 멍청하다고 생각하진 않았지만 절대적인 공부양이나 상대적인 공부 방법 조차 모든게 부족하다. 이걸 아는데 꽤 오랜 시간이 걸렸고 결론적으로 학원 즉 사교육이 아니면 난 공부를 못하는 녀석이다. 

혼자 공부를 못한다는 것은 엄청 게으르고 집중력이 부분도 있는 것 같다. 그러다 보니 혼자서 모든 것을 해결해야 하는 진정한 학습자의 태도와 습관에서 공부시간도 공부머리도 모든게 부족하다고 봐야할 것 같다. 

2021.01월 자책은 그만하고 2019년 이후 1년 정도 방황이라면 방황의 시기가 있었고 진유림/한정수 이 두분의 이야기들을 토대로 나도 다시 시작해보려고 한다. (사실 개발자 즉 프로그래머를 계속 해야 하나 생각도 들었었다) 

공부에 대한 얘기는 여기서는 길게 하지 않고 한정수님의 글을 따라 가면서 읽어본 내용들에 대해 단순히 기록만 한다. 

2021년은 나도 아래와 같은 후기를 남길 수 있어야 겠다. 기억보다는 기록이라는 키워드가 머릿속에 멤돈다.   

ryan-han.com/categories/%ED%9A%8C%EA%B3%A0/

 

회고 | Integerous DevLog

 

ryan-han.com

이 분이 회고를 쓰게 된 원천 글은 아래와 같다. 여기나온 Mandal-art 계획표는 나도 작성해 보았다. 

blog.devjoshua.me/2017/12/28/171228-2017%EB%85%84%ED%9A%8C%EA%B3%A0/

 

2017년 회고

QA에서 개발로 전직한 주니어 개발자의 블로그입니다

blog.devjoshua.me

그 안에는 또 학습에 실패한 이야기라는 우아한 형제들 글이 있다.

woowabros.github.io/experience/2017/12/11/how-to-study.html

 

학습에 실패한 이야기 - 우아한형제들 기술 블로그

프로그래머에게 지속적인 학습은 기본적으로 갖춰야 할 덕목 중 하나라고 생각합니다. 문제를 해결하는 방법들은 계속 발전하고 변해가며 하나를 배우면 오히려 배울 것이 늘어나는 경험을 항

woowabros.github.io

대표적인 성공사례인 이종립(기계인간) 님의 사례 설명도 있다 (이건 뭐.. 위인전으로 보자) 

okky.kr/article/425700

 

OKKY | OKKY 미니세미나 <비전공 학원출신 SI개발자, 유명스타트업 들어간.ssul> 참석 후기

안녕하세요! 오늘 비전공자 구직/이직준비 분들이 가장 관심 있을만한 세미나!에 다녀왔습니다! OKKY에서 주최하신 OKKY 미니세미나 비전공 학원출신 SI개발자, 유명스타트업 들어간.ssul 입니다.

okky.kr

OKKY 에 있는 글이라는데 자료구조, 운영체제, 네트워크, 알고리즘에 대한 중요성을 어필하고 있다 

okky.kr/article/314296?note=1036129

 

OKKY | 요즘 IT 신입 안뽑나요??

카카오는 아예 신입 사원이 씨가 말랐어요 ㅜㅜ 네이버는 작년 10월에 공채 뽑았다고 하지만.. 저같이 가을 졸업인 학부생은 IT 기업중에 마땅히 지원할 곳이 없는것 같아요

okky.kr

여기에 연결되는 네이버 CTO 송창현님의 글에 개발자로서 자세와 같은 얘기 나온다.

물론 대한민국을 대표하거나 기준점이 꼭 네이버는 아니지만 대외적인 목소리로서 필요한 부분에 대해 얘기하고 있어서 할말은 하는 회사로 생각해 보자 

digital.mk.co.kr/premium/share.php?no=10139

 

네이버 송창현 CTO의 `우리 회사 이렇게 채용한다`

[프리미엄 채용IR - 32] 네이버는 `60세가 돼도 현장에서 계속 코딩을 할 수 있는 사람`을 찾고 있습니다.  네이버 개발직군 채용을 총괄하는 송창현 최고기술경영자(CTO)는 무엇보다도 실력과 열

digital.mk.co.kr

이 분은 지금 포티투닷이라는 스타트업을 차리셨는데 Code42 일 때 2차면접에서 처참히 깨졌다.. 공부가 부족했는데 역시나 아직 시작못한 CS 영역에 대한 질문들을 제대로 대응하지 못했고 가비지 콜렉터 구조나 기타 코어적인 기술이론 부분에서 부족했다. 즉 공부했다면 대응가능한 부분들이었는데 왜냐하면 그 부분은 직접 코딩을 하거나 내가 만드는 부분이 아니라 잘 만들어진걸 사용하는 코어 시스템과 관련된 부분들이었다. 이런 내용들은 귓동냥으로 어서 얼핏 듣다만 내용들이라 포기할 수 밖에 없었다. 

회사정보를 찾아보다 인상깊었던 면접 후기도 보게 되었다 

blog.naver.com/dmatrix/221770545015

 

CODE42.ai 면접

미래에 자율주행이 사회에 엄청난 영향을 미칠수 있다고 생각하여 CODE42.ai에 지원을 하였다. 내가 가...

blog.naver.com

어짜피 다시 시작하는 부분이기 때문에 꼭꼭 곱씹어 다시는 같은 실수를 하지 말자. 준비된 사람이 되자.

조금 더 정확히 말하면 스스로 학습을 잘 할 수 있는 학습에 있어 성공할 수 있는 사람이 되어야 한다.

마지막으로 후기글을 보고 작성해본 나의 2021년 Mandal-art 차트를 남겨본다.

2021년 12월 내가 이것을 다시 보았을 때 나는 과연 발전하는 사람일까?

'My Life > who?' 카테고리의 다른 글

Applying JOB - Prologue  (0) 2017.10.01
2015년도에 쓴 글  (0) 2017.10.01
시작과 명분  (2) 2017.10.01

읽는 중


  1. 팀 개발을 위한 Git GitHub 시작하기 - [정호영, 진유림] "한빛미디어" 
  2. 모던 자바 인 액션 - [라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 / 우정은] "한빛미디어"
  3. 자바 ORM 표준 JPA 프로그래밍 - [김영한] "에이콘" 
  4. Concurrency in Go - [캐서린 콕스 부데이 / 이상식] "에이콘"

보유 중


  1. 자바 개발자와 시스템 운영자를 위한 트러블 슈팅 이야기 
  2. 자바 개발자도 쉽고 즐겁게 배우는 테스팅 이야기 
  3. 그림으로 공부하는 IT 인프라 구조 
  4. 쉽게 읽는 하드웨어 & 소프트웨어의 원리와 구조 
  5. 웹을 지탱하는 기술 
  6. 코딩을 지탱하는 기술 
  7. 자바 기반의 마이크로 서비스 이해와 아키텍처 구축하기 
  8. 초급 개발자들을 위한 가볍고 넓은 스프링 부트 스타트 스프링 부트 
  9. Think Data Structures 자바로 배우는 핵심 자료구조와 알고리즘 
  10. 자바 객체지향의 원리와 이해

읽은 책


 

'My Reading > Book' 카테고리의 다른 글

~ 2020 내가 읽은 개발 서적 정리  (0) 2021.01.24

+ Recent posts