두다지 서버 개발자 양성 프로그램 Week 3

3번 째 멘토링

지난 주 과제로 Test case를 포함한 해시 테이블과 우선순위 큐를 다시 직접 작성하였고, 작성한 우선순위 큐를 활용하여 간단한 스케줄러 토이 프로젝트를 만들어 보았다. 이 때, 기본적으로 작성한 우선순위 큐를 곧바로 사용하는 것이 아니라, 작업 스케줄러로 사용하기 위해 특정 method를 변환해서 사용해야 한다는 등의 생각을 하며 기본 자료구조의 활용을 고민해볼 수 있었다. 그리고 상속이 가진 문제점을 시작으로 하여, 상속의 문제점을 회피하되 그 장점은 살리기 위한 방법의 일환으로 나온 Strategy Pattern에 대한 이야기를 멘토님과 나누었다. 혼자 이해를 하려고 했을 때는 왜, 그리고 어떻게 해당 디자인 패턴이 현업에서 사용될 수 있을지에 대한 확실한 이해를 할 수 없었지만, 멘토님의 라이브 코딩을 보며 Strategy Pattern이 이처럼 효율적으로 사용될 수 있음을 느낄 수 있었던 귀중한 시간이었다.

Code Review

  • 현업에서의 코드 리뷰는 테스트 코드로 진행되는 것이 보통
  • 한 class는 400 line 정도로 작성하는 것이 좋음
    • 한 method는 40 line, 한 프로그램은 1000 line 이상 넘기지 않는게 좋음!
  • 화면 분할을 사용하는 프로그래머(ex. Vim user)들은 보통 한 파일에 몰아넣고 사용하는 경향이 있음
    • 반면에 파일을 쪼개서 작성하는 사람들도 있음. 즉, Case by case
  • Code convention 익힌 다음에는 안전하게 Editor 내 reformatting 기능 사용하는 것이 좋음
    • 익숙해지기 전까지는 연습하면서 공부!
  • python-fire(based on argparse) 등 잘 만들어진 module 사용
    • 핵심 Logic은 물론 본인이 구현해야 하지만, 다른 좋은 module을 같이 사용할 줄 아는 방법도 실력의 중요 요소
  • 대개 main thread는 UI로 하고, 이외의 기능들은 추가적으로 thread 생성해주어 사용하는게 보통

feedback

  • Class/method의 depth: prompt method 정도 길이로 작성하는 것이 딱 좋음
  • 공부 및 과제에 시간 투자 더 하기..

Why Interface?

  • 기계어로 모든 것을 작성하던 시절이 있었음...
    • 하나의 프로그램을 작성하는 것도 너무 어려움
  • 고수준의 C언어 등장. C가 가진 Struct(구조체) 개념
    • 사람의 생각과 직관을 실현시킬 수 있도록 만족시켜준 개념이자,
    • 서로 다른 크기의 메모리를 하나의 구조체에 담아 관리하는 것은 획기적인 개념
    • but, C언어 사용자들의 무분별한 goto문 분기로 인한 코드의 스파게티화가 문제가 됨
  • 함수로 가자!: 함수 단위로 packing 되어 내부에서 정돈되는 느낌
    • 프로그램은 사실 크게 보면 메모리와 메모리를 수정하려는 함수로 구성된 것임
    • 그러나, 함수 역시 모든 사람이 메모리를 접근하는 것에 대한 불만
  • Class 개념의 등장
    • 클래스의 특징: Class 내에 method와 멤버 변수가 존재
    • 기본적으로는 class 내에서 사용이 가능
      • 권한에 대한 문제: public, protected, private...
    • Heap에 형성된 메모리를 누구에게 사용 가능하게 할 것인지에 대한 강제성 및 규칙 부여 가능 -> encapsulation
    • 함수로 공동의 데이터를 고쳐나가는 것에서 Class로 자신에게 주어진 권한의 영역만을 사용하는 패러다임으로의 변화
  • 함수의 재사용: 상속을 통해 기존의 작성된 함수를 재사용해보면 어떨까?
    • ex) 개와 고양이의 '사료먹기'라는 행위

    Animal - Dog / Cat과 같이 작성하여 중복 메소드의 제거 가능

    • 상속이 없다면? 비슷한 작업을 하는데 계속해서 중복된 코드를 작성해야 함
    • 그러나, 상속이 남용됨에 따라 그 depth가 깊어지고 결과적으로 정신이 없어짐...
  • Interface의 등장으로 사람들은 상속을 멀리하기 시작함
    • 나에게 필요한 정보가 산재되어있는가?(Class) / 나에게 필요한 정보가 잘 정돈되어있는가?(Interface)
    • 실제로 Depth가 깊어진 상속 구조에서 method를 Override할 때, 하나의 오타라도 나면 해당 코드는 작동하지 않아야 하는데 작동되는 경우가 생김
    • 즉, 점점 구조적 어려움으로 오류를 찾기가 어려워짐
  • Interface라는 하나의 Summary note를 통해 주어진 기능들을 일목요연하게 보여주면 사용자의 이해를 돕고, 일종의 강제성도 부여할 수 있게 됨
    • 특정 행동에 대한 강제는 하지만, 그 내부적 구현은 해당 Interface를 사용하는 Class의 특성에 따라 다르게 설정할 수 있음
      • ex) Queue / Priority Queue / Circular Queue의 Interface: peek, pop, push의 methods
      • ex) Scheduler (Priority Scheduler / Round Robin Scheduler)의 Interface: add, delete, peek, pop의 methods
  • Interface를 자체적으로 지원 안하는 인터프리터 언어를 사용하는 개발자들도 Class를 Interface와 같이 사용하는 경우가 많아지고 있음
  • method 구현과 관련한 부분을 외부에서 넣어주는 것이 Strategy Pattern
    • 상황에 따라 바꾸어 사용할 수 있는 것이 Strategy!
    • 그러나 같은 method가 반복된다? 그냥 코드의 재사용으로 인정(아래 예제의 dummy_action 참조)
      • 상속으로 중복 method를 받아오게 되면 다시 그 method가 어느 부모 클래스에 속하는지 모르는 상속의 문제 발생
  • Java의 Decorator Pattern 역시 본질은 Interface의 구현과 관련된 문제
    • Interface를 어떻게 사용하는 것에 따라 달라지는 것이 Design Pattern
  • Strategy Patter의 예
def dummy_action():
    print('do a job')


class IEvent:
    def __init__(self):
        pass

    def run(self):
        assert False


class BackupPolicy(IEvent):
    def __init__(self):
        pass

    def run(self):
        dummy_action()


class ReportPolicy(IEvent):
    def __init__(self):
        pass

    def run(self):
        dummy_action()


class ISchedulePolicy:
    def __init__(self):
        pass

    def has_target(self):
        assert False

    def pop(self):
        assert False

    def add(self):
        assert False


class RoundRobinSchedulePolicy(ISchedulePolicy):
    def __init__(self):
        pass

    def has_target(self):
        # TODO
        pass

    def pop(self):
        # TODO
        pass

    def add(self):
        # TODO
        pass


class PriorityQueuePolicy(ISchedulePolicy):
    def __init__(self):
        pass

    def has_target(self):
        # TODO
        pass

    def pop(self):
        # TODO
        pass

    def add(self):
        # TODO
        pass


class Scheduler(object):
    def __init__(self, schedule_policy):
        self.schedule_policy = schedule_policy

    def loop(self):
        sp = self.schedule_policy
        while True:
            if sp.has_target():
                event = sp.pop()
                event.run()

    def add(self, event):
        sp = self.schedule_policy
        sp.add(event)


if __name__ == "__main__":
    # schedule_policy = RoundRobinSchedulePolicy()
    schedule_policy = PriorityQueuePolicy()
    Scheduler(schedule_policy).loop()



Computer Architecture - Pipeline

  • 메인 보드의 Clock에 맞추어 Task들이 동작
  • 파이프 라이닝은 일종의 컨베이어 벨트화
    • 각각의 독립된 Core에 To-do list를 전달
    • 하나의 Core는 주어진 하나의 일만 끝내고 쉬는 것이 아니라 부여받은 List의 일을 계속해서 수행
    • 이론적상으론 Core 갯수만큼의 성능 향상 얻을 수 있음
      • 마냥 행복할 수는 없음: Pipeline Hazard
  • 각 Core는 완료한 작업을 넘길 수 있는 매개체가 있어야 함(Memory, disk...)
  • Core에서 끝난 각 Task를 다음 Core에게 direct하게 넘기는게 아니라, Queue 구조를 사용하는 것이 일반적
    • Direct하게 전달할 시, 속도에 따른 불균형이 생길 수 있기 때문
  • 단순히 CA의 개념에서 그치는 것이 아니라 실제로 Scrapy와 같은 소프트웨어에서도 파이프라이닝 사용
    • 기본 개념을 알면 실생활에서 '이렇게도 사용할 수 있지 않을까?'라는 생각을 할 수 있게 됨

다음 주 까지의 assignment

  • Cache, Pipeline, I/O interrupt, 시스템 버스 학습해오기 from <컴퓨터구조론>
  • <리눅스 커맨드 라인 완벽 입문서> 읽고, 중요 개념 정리하기
  • vim 익히기
    • Tool에 익숙해야 내 실력을 온전히 발휘할 수 있음


+ Recent posts