본문 바로가기

짜투리

[데이터 처리] 도로명주소x상세주소 처단 일지

728x90

 

 

 

 

 

 

 

 

 

 

 

 안녕하세요. 

 

 현재 큼직한 프로젝트가 끝났습니다. 매우 큰 함성. 와아아앙.

 

 이 프로젝트에 쓰인 데이터가 주소누리집의 도로명주소 한글상세주소 표시라는 데이터입니다.

 

 이번에 쓸 글은 이 데이터를 다루면서 있었던 과정입니다. 그때 간단하게 적어놨던 글이 있는데, 그 글을 바탕으로 이번 게시물 작성하겠습니다. 

 

 프로젝트 내용에 관해서는 언급하기 어려워서. 얼랑뚱땅 넘어가는 부분이 있을 수 있습니다. 귀엽게 봐주세요.

 


1. 데이터

 

 도로명주소는 우리에게 매우 친숙한 데이터이죠. 제가 사용할 데이터인 도로명주소 한글과 상세주소 표시는 각각 24개, 18개의 컬럼(24년09월기준)을 가졌고, 각 컬럼에 대한 정보는 아래 링크에서 확인할 수 있습니다. 

 

 

https://business.juso.go.kr/addrlink/elctrnMapProvd/geoDBDwldPubList.do?cPath=99JD

 

공개하는 주소

※ 도로명주소 공간정보의 건물객체는 국가주소정보시스템에서 최초 생성되며, 데이터가 일단위 갱신 관리되어 신속성과 정확성이 우수하므로 공간정보 구축 시 도로명주소를 위치식별자로

business.juso.go.kr

 

 

1.1 데이터세트 선정

 처음 데이터세트 선정부터 쉽지는 않았습니다. 구체적으로 도로명주소 한글 데이터를 쓸지, 건물DB를 쓸지 고민이 컸습니다. 

 

https://business.juso.go.kr/addrlink/elctrnMapProvd/geoDBDwldPubList.do?cPath=99JD

 

 

 간단하게 두 데이터세트의 차이를 말씀드리자면, 도로명주소 하나에 여러 건물이 있을 수 있으니, 건물DB가 좀 더 상세한 정보라고 할 수 있습니다. 

 

  도로명주소 한글 건물DB
주키 도로명주소관리번호
= 시군구코드(5) + 읍면동(3) + 도로명번호(7) + 지하여부(1) + 건물본번(5) + 건물부번(5)
건물관리번호
= 시도(2)+시군구(3)+읍면동(3)+리(2)+산여부(1)+지번본번(4)+지번부번(4)+시스템번호(6)
설명 도로명주소 하나에 관한 번호 도로명주소 내 건물 하나에 관한 번호
예시 EX) 00대학교 EX) (00대학교) 학생생활관, 인문관 등

 

 

 프로젝트 매니저님과의 여러 번 회의를 거쳐서. 도로명주소 한글을 선택하게 되었습니다. 가장 큰 이유는 건물DB의 건물명은 일반적으로 도로명주소의 상세주소로 들어가거나, 그마저도 들어가지 않는 경우가 많았기 때문입니다. 

(+) 경기도 과천시 별양로 111 (별양동, 주공아파트) => 여기서 빨간색을 도로명주소, 나머지를 상세주소로 봅니다.

 

 다른 이유로는, 도로명주소관리번호의 경우는 도로명주소를 알면 유추하기 쉽지만, 건물관리번호의 경우 '시스템번호'라는 값이 얻기 어려웠습니다. 프로젝트에서 도로명주소 값이 있는지 확인하기 위해, 주키값의 존재여부를 확인하는 과정이 필요했기에, 더더욱 도로명주소 한글을 선택하게 되었습니다. 

 

 

1.2 데이터 읽기

 도로명주소 한글상세주소 표시 데이터는 2가지 종류의 파일이 각각 17건 씩 존재합니다. 각 데이터세트마다 도합 34건의 txt파일이 존재한다는 의미입니다.

 

  도로명주소 한글 상세주소 표시
파일 종류 1. jibun (ex: jibun_rnaddrkor_busan.txt)
2. roadname (ex: rnaddrkor_busan.txt)
1. rnspbd (ex: rnspbd_adrdc_busan.txt)
2. rnspbt (ex: rnspbt_adrdc_busan.txt)
(rnspbd: 지자체 담당자가 입력, rnspbt: 건축물대장)
사용 파일 종류 roadname rnspbt
총 인스턴스 건수 6,398,602 21,073,650

 

 

 실제 데이터는 아래처럼 '|'로 구분되어 있습니다.

 

상세주소 표시(2409월분) 중, rnspbt_adrdc_busan.txt

 

 

 데이터를 딱 봤을 때는 그냥 데이터프레임으로 읽으면 될 것 같지만. 상세주소 표시의 경우 그렇게 간단하지는 않았습니다. 

 

(왼) 도로명주소 한글 dtype (오) 상세주소 표시 dtype

 

 

 데이터프레임의 object형이란, 문자열이거나 문자 숫자 등이 복합적으로 섞여져 있는 값을 말합니다. 

 

 공식 홈페이지에서는 숫자(int, float 등)라고 명시했지만. 실제로 읽어보면 object형인 경우도 있으니, 실제값을 확인해보면 좋을 것 같습니다.

 

상제주소 표시 레이아웃 중 일부

 

df = pd.read_csv(f'{folder_path}/{filename}', sep="|", low_memory=True, names = columns, encoding=encoding)

 

 

 위 코드는 도로명주소 한글 데이터를 읽을 때 사용한 코드 중 일부입니다. (지금 든 생각인데 f-string을 쓰기보다 os.path.join을 쓸 걸 그랬네요)

 

 sep은 구분자를 설정하는 거지만. 데이터에 "(따옴표)나 ,(콤마)가 있는 경우는 구분자 설정이 크게 의미가 없는 것 같습니다. 

 

상세주소 표시(2409월분) 중,rnspbt_adrdc_daegu.txt

 

위 인스턴스의 실제 주소 값

 

 

 위 예시를 보면 실제 값에 따옴표나 콤마가 섞여있어서 데이터프레임에 이상하게 저장되는 일이 있었습니다. 

 

 그래서 상세주소 표시도로명주소 한글과 달리, 아래 코드처럼 file로 읽어와서 특정 문자열('|')을 지정하여 분리하는 식으로 데이터를 읽어왔습니다.

 

lines = []
# 파일 열고 따옴표 제거
for encoding in [ "utf-8", "utf-8-sig", "euc-kr", "cp949" ]:
    try:
        with open(f'{folder_path}/{filename}', 'r', encoding=encoding) as file:
            for line in file:
                line = line.replace('\'', '')  
                lines.append(line.replace('"', '')) 
        break
    except:
        continue

lines = [line.rstrip('\n') for line in lines]
data = [line.split('|') for line in lines]
df = pd.DataFrame(data, columns=columns)

 

 

 사실 위 방법도 완전하다고 생각하진 않습니다. 세상 노이즈가 저 2개만 있는 것도 아니고. 매번 새로운 노이즈를 찾을 때마다 코드를 수정해야 하나 싶기도 하고. 맞는 인코딩을 찾기 위해 for문과 try-except 조합을 쓰는 것도. 뭐랄까... 펀쿨섹 하지 않다고 해야될까요. 

 

 좀 더 멋있는 방법이 있다면 댓글 남겨주세요.

 

 

 이 주제와 논외로 인코딩에 관한 이야기는 빼놓을 수 없습니다. 관심이 없다면 다음 챕터로 넘어가시면 됩니다. 

 공공데이터포털의 csv파일 약 100여건을 처리하는 과정에서. 인코딩 생각 안 하고 무작위 10건 정도 테스트한 다음에. 나머지 100여건을 돌리고 퇴근한 적이 있습니다.(데이터프레임에서 인코딩 설정 안 하면 기본은 utf-8임) 

 서로 다른 도메인에서 업로드한 파일이이게 당연히 인코딩이 통일되어 있지 않았고. 아침에 출근해보니 멈춰있었습니다.(오류 나면 다음 파일로 넘어가는 예외구문도 작성하지 않는 실수를 저질렀죠. 지금 생각해보면 코드를 모듈화 안 하고 일자로 작성해서 그게 문제인지도 잘 몰랐던 거 같음.) 

 아무튼. 그 뒤로 인코딩과 모듈화에 집착하게 되었습니다. +주석(이 코드가 내 코드라고 왜 말을 못해!!)

 그리고 처리한 데이터를 저장할 때 윈도우에서 바로 읽고 싶어서 euc-kr로 저장한 적도 있었습니다.(윈도우 기본 인코딩은 euc-kr, 윈도우상으로는 ANSI) UnicodeDecodeError로 기억하는데. 웬만하면 utf-8로 저장하고. 윈도우 기본 인코딩을 utf-8로 변경하는 게 좋은 것 같습니다.(vscode 상에서 utf-8로 해야 간단하게 확인가능함)

 


2. 데이터 처리 변천사

 

 도로명주소 한글상세주소 표시는 '도로명주소관리번호'라는 값으로 연결할 수 있습니다. 그럼 작업을 크게 2가지로 나눌 수 있습니다.

 

1. 도로명주소 텍스트에서 도로명주소관리번호 추출 

(+) 도로명주소 한글은 도로명주소 풀텍스트가 아니라 시군, 시군구, 도로명 등으로 제공하기 때문에, 풀텍스트로 만드는 과정도 필요함. 근데, 세종특별자치시 같이 시군구가 통으로 없는 경우나 건물부번이 0인 경우 등 생각보다 풀텍스트로 변환하는 과정도 간단하진 않았음.

2. 도로명주소관리번호에 맞는 상세주소 데이터프레임 추출

 

 굉장히 간단해 보이지만. 실상은 4번의 시도 끝에 어느 정도 만족스러운 결과물을 낼 수 있었습니다. 바쁘신 분은 2.4만 보면 될 것 같습니다.  

 

(+) 제가 맡은 파트는 2번 작업이라서, 1번 작업에 관한 정보는 부족합니다.

 

번호 1번 작업 2번 작업 4만건 기준
처리시간
1 mysql 쿼리 1h 40m
2 trie datafram 40m
3 trie dict(id:dict(col:list)) 3m
4 trie dict(id:dict()) 20m

 

 2.1 mysql 쿼리

 말 그대로 1번 작업과 2번 작업을 mysql 쿼리로 한 경우입니다. (모든 데이터를 mysql 서버에 올리는 건 덤)

 

 아시는 분은 알겠지만 IO 속도는 네트워크>파일>메모리 순으로 느립니다. 즉, mysql 쿼리를 날린다는 건 속도가 매우 느리다는 것이죠. (이걸 모르는 건 아니였지만. 모종의 이유로 하게 되었습니다)

 

 

 먼저 테이블 구조를 정의합니다. ddl은 전공생이면 다 아시겠죠. 예시를 보여주자면 아래와 같습니다.

 

CREATE TABLE detailed_address2 (
    `number` INT NOT NULL AUTO_INCREMENT,  
    `roadname_ID` VARCHAR(26) NOT NULL,
    `dong` VARCHAR(50),
    {생략}
    `address_number` INT,  
    `address_roadname_ID` VARCHAR(26), 
    PRIMARY KEY (`number`),
    FOREIGN KEY (`address_number`, `address_roadname_ID`)
        REFERENCES road_land_address2(`number`, `roadname_ID`), 
    INDEX idx_address_search (`roadname_ID`, `dong`, `floor`, `room`)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

 

 

 여기서 INDEX를 설정해주면 검색속도가 빨라지고, 맨 하단에 CHARACTER SET은 한글 인코딩 설정해주는 것입니다. 안 해주면 한글이 깨지더라고요.  

 

 이렇게 ddl 파일을 정의하고 .sql 파일로 저장해줍니다. 그리고 아래 코드를 실행하여 테이블을 정의했는데, 오류가 나는 경우가 있습니다. 저는 이 때, .sql 파일에 작성된 쿼리가 여러개(create문이 여러개였다는 의미) 여서, 하나 빼고 다 주석 처리한 다음에 실행했을 때 오류가 나지 않았습니다. 

 

 mysql workbench를 써도 되는 과정입니다.

 

import pandas as pd
import os
from sqlalchemy import create_engine
import pymysql
from tqdm import tqdm

# MySQL 연결 설정
connection = pymysql.connect(
    host='{IP}',
    user='{username}',
    password='{password}',
    database='{사용할 데이터베이스 이름}',
    port = {port},
    charset='utf8mb4'  # UTF-8 인코딩 설정
)

# DDL 파일 읽기 및 실행
ddl_file_path = '{ddl파일 경로 설정}.sql'

with open(ddl_file_path, 'r', encoding='utf-8') as file:
    ddl_script = file.read()

# 커서를 통해 DDL 스크립트 실행
with connection.cursor() as cursor:
    cursor.execute(ddl_script)
    connection.commit()

connection.close()

 

 

 그리고 나서 데이터를 넣습니다. 데이터는 미리 판다스의 데이터프레임으로 준비된 상태였습니다.

 

engine = create_engine("mysql+pymysql://{username}:{password}@{ip}:{port}/{databasae}")
{미리정의한데이터프레임}.to_sql(name='{table}', con=engine, if_exists='append', index=False)

 

 

 데이터가 잘 들어갔는지 확인할 때는 아래 코드를 사용했습니다.

 

pd.read_sql("select * from {table} limit 3;", con=engine)

 

 

 위 ddl에서 정의한 것처럼. 상세주소 표시도로명주소 한글의 도로명주소관리번호 등을 외래키로 사용합니다. 따라서 데이터프레임에서 pd.merge를 사용하여 데이터프레임을 새로 만들어줬습니다.

 

merged_df = pd.merge(
    {상세주소표시데이터프레임},
    {도로명주소한글데이터프레임},  
    left_on='roadname_ID',  # 상세주소표시의 도로명주소관리번호
    right_on='address_roadname_ID',  # 도로명주소한글의 address_도로명주소관리번호
    how='left'
)

 

 

 이렇게 올리고나서. ssh와 파이썬 코드를 사용해서 쿼리 날리고. 테스트하고. 코드짜고. 끔찍. 중간에 mysql 서버 끊겼을 때는 조금 울고 싶었습니다.(pc 문제) 

 

 사실 대용량 데이터는 읽어오는 시간이 있기에 mysql을 쓰는게 좋지만. IO가 너무 느려서 30만 건 짜리 처리하는데 89일이 걸리는 대참사가 벌어지기에. 프로젝트에 따라 차라리 초반 읽어오는 시간을 기다리고 빠르게 처리하는 게 좋을 것 같습니다. 

 

 

 추가적으로. 코드를 자세히 보면 알겠지만, 테이블을 정의할 때는 pymysql을, 데이터를 삽입할 때는 sqlalchemy의 create_engine을 사용했습니다. 

 

 파이썬으로 DB 다뤄본 경험이 적어서, 어쩌다보니 2가지 라이브러리를 사용했었는데. 2가지의 차이점을 안 알아볼 순 없죠. 관심이 없다면, 다음 챕터로 넘어가시면 됩니다. 

 

 ChatGPT의 답변은 아래와 같습니다.(너무 신뢰X)

 

 

ORM(Object-Relational Mapping): 데이터베이스와 객체 지향 프로그래밍 언어 간의 호환되지 않는 데이터를 변환하는 프로그래밍 기법. 

 

 pymysql의 경우 cursor.excute({sql})를 통해서 데이터베이스간 상호작용이 가능했습니다. sqlalchemy의 경우는 dataframe.to_sql({테이블 정보, 데이터베이스정보 등의 매개변수})를 통해서 가능했구요. 

 

 sql문을 몰라도 사용할 수 있는 sqlalchemy의 활용도가 더 높다고 생각했습니다.(물론 이 라이브러리에서도 sql문을 보낼 수 있지만) 무엇보다 데이터프레임을 매우 사랑하는 사람으로서, 데이터프레임에 sql문을 적용할 수 있다는 점이 좋았습니다.(pymysql의 반환 값은 tuple이라고 함. 그래서 sqlalchemy를 할 때 추가적인 조작-튜플을 데이터프레임으로 변환-없이 사용할 수 있었음)

 

 2.2 trie+dataframe

  trie를 기억하십니까. 전공생들이라면 자료구조 시간에서 트리 구조를 배운 기억이 있을 겁니다.

 

 1번 작업인 도로명주소에서 도로명주소관리번호로 추출하는 과정 +알파가 있습니다. 아무튼 그 과정에서 trie 구조를 써서, 1번 작업 속도는 꽤나 개선되었습니다.(이 부분이 바로 얼랑뚱땅 부분입니다.)

 

 지금부터는 2번 작업만 말하겠습니다.

 

 

 2.1의 뼈아픈 실패를 경험하며. 파일을 읽어와 데이터프레임으로 작업을 하여. 4만 건 기준 처리 속도를 1h 40m=>40m으로 줄였으나. 사실 이 방법도 너무 느렸습니다. 

 

 아무래도 데이터프레임이 검색 목적이 아니다보니 필터링 시간이 오래 걸렸던 거죠.

 

 데이터프레임은 Q(n), 딕셔너리 Q(1)이라는 사실. 그래서 바로 딕셔너리로 바꿨습니다. 

 

 2.3 trie+dict(id:dict(col:list))

 판다스 데이터프레임은 to_dict라는 아주 귀여운 기능이 있습니다. 말 그대로 데이터프레임을 딕셔너리로 바꾸는 함수이죠. 

 

 뜬금없지만. 이때 작성한 메모를 보여드리겠습니다. 

 

 

 별 의미는 없고. 이때 제가 써놓은 게 좀 귀여워보여서 넣었습니다. 자기자신 모에화.

 

{도로명주소관리번호1: {동명칭: [1,2,3,4],
                      호명칭: [1,2,3,4]
                    .
                    .
                    .},
 도로명주소관리번호2: {동명칭: [1,2,3,4],
                     호명칭: [1,2,3,4]
                    .
                    .
                    .}}

 

 

 대략 이렇게 데이터를 저장했다는 의미입니다. 

 

 이 방법의 문제는 상세주소 데이터의 경우, 동명칭>층명칭>호명칭. 이런 식으로 특정 동의 특정 층, 특정 호가 있기 마련인데. 리스트로 저장하면 그 종속성을 알 수 없게 된다는 것입니다. 실제 데이터는 모든 건물의 모든 동층호가 일정하지 않기 때문에 더더욱 문제입니다.(ex: a아파트의 1동은 10층까지 있고, 2동은 1층이 없고, 7층까지 있음)

 

 상당히 바보같은 방법이니 절대 따라하지 마세요. 빠르게 하려는 것에 치중해서 데이터를 망쳐버린 케이스입니다.

 

 아무튼 이때는, 저런 방식으로 데이터를 json으로 저장하고, 도로명주소관리번호를 딕셔너리에 검색했습니다.  

 

 

 2.4 trie+dict(id:dict())

 드디어 왔습니다. 짝짝짝. 

 

 데이터를 만드는 데는 시간이 걸리긴 하지만(약 2천만 건 기준 2시간 넘게 걸림). 원본 데이터를 그대로 저장하는 방법입니다. 한 번 만들어 놓으면 읽는 것 자체는 오래 걸리지 않는다는 장점도 있죠.

 

    # 도로명주소관리번호 기준으로 데이터 합치기     
    detailed_df2 = pd.merge({도로명주소한글 데이터프레임},
                        {상세주소표시 데이터프레임},
                        left_on="도로명주소관리번호",
                        right_on="도로명주소관리번호",
                        how="left")

    # 데이터프레임을 '도로명주소관리번호'로 그룹화
    grouped = detailed_df2.groupby('도로명주소관리번호')
    detailed_dict = {}
    for id, group in tqdm(grouped, total=len(grouped)):
        # group에서 필요한 컬럼들만 추출
        detailed_dict[id] = group[{필요한 컬럼 리스트}].to_dict(orient='records')
        
    print("상세주소표시 JSON 저장 중")
    with open(os.path.join({저장할 경로}, '{파일이름}.json'), 'w', encoding='utf-8') as json_file:
        json.dump(detailed_dict, json_file, ensure_ascii=False, indent=4)

 

 

 tmi이지만. 저는 tqdm을 참 좋아한답니다. 그리고 데이터프레임에서 검색 속도를 빠르게 하는데는 groupby가 진짜 좋아요. 

 


3. 병렬 처리

 

 대량의 데이터를 처리하는데 병렬 처리만큼 좋은 건 또 없죠. 여기서 대량의 데이터는 2가지 의미로 나눌 수 있는 것 같습니다.

 

1. 데이터 한 건 자체가 매우 큰 경우

2. 데이터 자체가 무수히 많은 경우 

데이터를 파일로 치환해서 보면 더 이해하기 쉬울 것 같습니다. 

 

 

 저는 1,2번 둘다 해당 되었는데. 2번 케이스에 맞는 병렬처리를 했습니다.

 

 코드 짤 때 아래 문서를 참고했습니다. 

 

https://docs.python.org/ko/3/library/concurrent.futures.html

 

concurrent.futures — Launching parallel tasks

Source code: Lib/concurrent/futures/thread.py and Lib/concurrent/futures/process.py The concurrent.futures module provides a high-level interface for asynchronously executing callables. The asynchr...

docs.python.org

 

 어차피 위 문서 요약한 내용이므로 메모한 내용을 복붙하겠습니다.

 

 

 파이썬 병..렬처리에 대해 알아보자.

 

 concurrent.futures에 2가지 하위 클래스가 있다.

 

1. ThreadPoolExecutor

2. ProcessPoolExecutor

 

 이름 그대로 스레드, 프로세스를 통해 병렬처리를 할 수 있다. 파이썬 문서에서 비동기 어쩌구 하는데. 비동기는 쉽게 말해 병렬처리다. 하나의 작업을 끝내고 다음 작업을 하는 컴퓨터에게. 여러 작업을 동시에 시키는 극악무도한 짓을 시키는 거다.

 

 추상 클래스를 보면, submit map 등이 있는데. submit는 한 번에 하나의 작업을 예약하고, 다른 작업을 할 수 있게 하고. map은 여러 작업을 한 번에 예약한다고 한다.

 

 즉, sumbit는 하나의 작업을 예약 걸어놓고 다른 작업도 예약 걸어놓고.. 이런 식으로 병렬처리가 가능하고. map은 한 번에 모든 작업 예약 거는 방식인 듯하다.

map이라는 함수가 원래 iterable한 데이터 대상으로 그런 식으로 작동하긴 함.

 

 덧붙여서. 프로세스와 스레드의 차이점을 알아보자.

 

1. 프로세스: 하나의 작업을 독립적으로 수행.

2. 스레드: 프로세스 안에서 여러 스레드로 나뉠 수 있으며, 서로 정보를 공유함.

 

 쉽게 말하면. 컴퓨터로 게임하면서 유튜브하면서 디스코드로 친구들이랑 연락한다치면. 게임, 유튜브, 디스코드가 하나의 프로세스고. 그 안에서 스레드가 열심히 돌아가는 모양새.

 

 차이점 하나를 꼽자면. 스레드는 독립적이지 않다보니, 하나의 스레드에 에러 뜨면, 다른 스레드에도 영향을 받는다고 함.

 

from concurrent.futures import ThreadPoolExecutor

# 데이터 처리 함수
def process_file(filename, target_folder_path, save_path):
	{생략}

# 병렬 처리 함수
def parallel_process(file_name_list, target_folder_path, save_path):
    with ThreadPoolExecutor() as executor:
        futures = [executor.submit(process_file, filename, target_folder_path, save_path) for filename in file_name_list]
        for future in tqdm(futures, total=len(file_name_list)):
            future.result()  # 결과를 기다림

if __name__ == "__main__":
    # 필요한 데이터 정의
    file_name_list = [dataset for dataset in os.listdir(target_folder_path) if dataset.endswith('csv')]
    
    print("필요한 데이터 정의....")

    # 병렬 처리로 파일 단위로 정제 시작
    parallel_process(file_name_list, target_folder_path, save_path)

 

 

 만약 코드를 서버에서 돌린다면. 알만한 사람은 아시겠지만. 꼭 nohup을 쓰세요. 

 

nohup python {실행할 파이썬 파일 이름}.py > {결과물을 저장할 파일} &

 

명령어 설명
nohup 서버 세션이 끊겨도 계속 실행함
python {실행할 파이썬 파일 이름}.py .py 돌리는 명령어
> {결과물을 저장할 파일} .py에서 출력되는 결과물을 파일에 저장
(>은 덮어쓰기, >>는 붙여쓰기)
& 백그라운드에서 돌림(현재 터미널에서 print되지 않는다는 뜻)

 

 

 돌리면 아래처럼 돌아갑니다. 

 


4. 소감

 

 이번 프로젝트를 하면서 느낀 점이랄까요. 생각한 점은 다음과 같습니다.

 

1. 코드와 데이터 관리는 매우 중요하다. 같은 프로젝트를 하고 있다면 더더욱. 깃 관리 별 다섯개.

2. 기록. 진행상황을 문서로 작성하는 건 매우 중요하다.(누구든 이해할 수 있는 언어로) 나중에 매우 큰 자산이 됨.

3. 너무 자책하지 않아도 된다. 스스로를 더 믿자.

 

 요즘 논어를 읽고 있습니다.(무맥락)

 

 거기에는 "배우고 때때로 익히니 기쁘지 아니한가"라는 말이 있습니다. 옛날엔 그냥 그렇군!하고 말았는데. 이번 프로젝트를 통해서. 이 말이 가깝게 느껴졌습니다. 

 

 저 문장에는 '배우다'와 '익히다'가 있는데. 배운다는 건 학교 수업 시간에 공부하고 이해한 것들(데이터베이스, 프로그래밍언어, 자료 구조 등등)이고. 익힌다는 것은 이것을 자신이 원하는대로 응용한다는 것(프로젝트)인 것 같습니다. 배우고 익히지 않으면 의미가 없는 것이죠. 

 

 그래서 수업 시간에 시험을 위해 공부했던 것들을 총망라하는 프로젝트를 경험할 수 있어서. 굉장히 값진 시간이었습니다.

 


 

 부족한 글 읽어주셔서 감사합니다. 

 

은 지금부터 시작입니다