책 읽듯이 코드 읽기

hur-kyuh-leez·2019년 11월 5일
0

Prologue

프로그래밍도 결국 언어 이다.

프로그래밍을 처음 접하는 사람들에게
내가 항상 강조 하는게 있다.
프로그래밍도 언어라고
또한,
영어에 대해 어려움을 겪는 사람들에게 똑같은 이야기를 한다.
그러면 언어를 배울 때 가장 효과적인 방법은 무엇일까?

어렸을 때 배우면 된다.

하지만, 이미 나이든 사람에게는 해당이 안된다.
나 또한 영어를 배울 때,
뉴욕에 처음 갔을 때가 초등학교 4학년 이였는데,
조기 유학 치고는 많이 늦었었다.
반대로 3학년 이였던 동생은
발음 + nuance + 문화 이해도에서 나보다 훨씬 앞써 나간다.
언어는 무조건 어렸을 때 습득 해야한다.

팩트폭행을 당했으니,
이제 우리가 '언어'를 쉽게 배울 수 있는 방법에 대해서 모색해보자.
아이와 어른의 차이는 많다.
하지만 학습을 할 때 중요한 것은
어떤 방식으로 정보를 습득하느냐가 아닐까?

어렸을 때로 돌아가 보자.
'아빠', '엄마' 라는 소리를 배우기 위해서
어른들이 많은 input이 있었다.
한 만번정도 아빠, 엄마라는 소리를 들으면
아이도 따라서 옹얼거릴 것이다.

그럼 이 과정을 인공지능 관점에서 봐보자.
모델을 트레이닝 하려면 많은 데이터가 필요하다.
데이터는 어른들이 소리를 내면서 많이 주입해 준다.
모든 데이터가 주입되고 보존되는건 아니다.
메모리상에 저장 되는게 있고 아닌게 있다.
아이는 소리라는 수많은 raw 데이터를 통해서
자신만의 '소리 흉내기' 모델을 만들 것 이다.

반대로 어른들을 보자.
어른들에게는 수많은 모델들이 뇌에 저장되어 있다.
그 중에 많은 부분을 차지하는 모델이 '언어'라는 모델이다.
어른들은 이 모델이 있기 때문에,
다른 언어를 습득을 할 때,
아이 처럼 '아빠', '엄마'라는 단어를 배울 때
몇 초가 걸리지 않는다.
하지만 이 과정에서 소리라는 raw data가
자신만의 model을 거쳐 compressed data가 된다.
근데 잘 생각해보면 어른과 아이의 training data가 자체가 다를 수도 있다.
보통 언어를 배울 때,
어른들은 글(눈으로)로 먼저 배운다.
반대로, 아이는 보통 소리(귀)로 배운다.
1. 어른들은 한국어 overfitting된 model이 이미 만들어져 있다.
2. 어른들은 raw data input이 다르다.

위 두가지 이유만으로도
어른들이 언어를 습득할 때,
아이들 보다 뒤쳐질 수 밖에 없다.

그러면 다 커서 언어를 습득 하는 건 불가능 할 까?
절대 아니다,
아이와 동일한 training data format으로 습득하면
일반적으로 배우는 것 보다
더 정확하게 배울수도 있다.
글로 배우지 말고 소리로 배우면 된다.
레벨은 딱 유아가 듣는 소리 부터 듣자,
먼저 미드, 영화, CNN, 등등 어른들이 먼저 듣는 것을 하면
절대 안된다.
생각해봐라 걷지도 못하는 아이가
어떻게 미드, 영화, CNN 보고 이해를 할까?
그리고 요새는 앱의 도움을 받아
내가 정확한 발음을 하는지 안하는지도 알 수 있다.

근데 사실 발음과 뉴앙스를 두고 공부하는 어른들은 없을 것 이다.
보통 각종 시험 때문에 영어를 배울려고 한다.
그럼 먼저 영어를 어떻게 비약적으로 습득할 수 있는 지 알아보자.
가정은 읽기 위주의 시험이고 글을 이해를 해야 높은 점수를 얻을 수 있다.
1. 일단은 training set을 찾아보자.
2. 모의고사를 다 찾아서 프로그램에 넣어 높은 출제비중의 단어를 외운다.
1,2은 보통 시중에 있는 많은 문제집이 대신 해준다.
1번을 잘 생각해보자 왜 이렇게 해도 되는지...
overfit 되던 안되던 상관이 없다. 시험 패스만 하면 된다.
그리고 시험은 이전 시험들이랑 매우 유사하다.
2번도 잘 생각해보자,
영어 사전에 있는 모든 단어를 외우지는 못한다.
근데 왜 3번은 없을까? grammar는 언제함?
grammar없이 문장해석이 가능하다.
그리고 문제를 풀 때 문장을 완벽히 해석할 필요가 없다.
대충 의역을 해도 문제 푸는데는 지장이 없다.
그럼 어떻게 때려 맞추기식을 하는 지 보자.

"Designing applications that can program computers is a long-sought grail of the branch of computer science called artificial intelligence (AI)."
출처: https://www.futurity.org/artificial-intelligence-bayou-coding-1740702/

이 문장을 보자.
단어 하나, 하나를 번역을 해보자.

{디자인ing}, {앱}, {그}, {할 수 있다}, {프로그램}, {컴퓨터}, {는}, {하나}, {긴}, {찾는}, {찾을 수 없는 진리}, {의}, {그}, {가지}, {의}, {컴퓨터}, {과학}, {부르다}, {인위적}, {지능}

이렇게 우리에게 a bag of words가 생겼다.
물론 context에 맞는 단어를 선택했는지는 의문이다.
그래도 한번 찍어 보자.
찍는 과정...
단어를 보니 컴퓨터가 두 번 이상 나왔고 앱, 프로그램 이라는 것도 연관이 있는 것 이다.
어찌됐던 무슨 컴퓨터 프로그램에 대해서 말하는 거 같다.
데이터를 짤라보자.
대충보니 {그}, {는}, {의} 기준으로 짜르면 될 거 같다.

{디자인ing}, {앱}, ~= 앱을 다자인하다
{할 수 있다}, {프로그램}, {컴퓨터} ~= 컴퓨터는 할 수 있다.
{하나}, {긴}, {찾는}, {찾을 수 없는 진리} ~= 찾을 수 없는 진리를 찾다
{가지} = {가지}
{컴퓨터}, {과학}, {부르다}, {인위적}, {지능} ~= 컴퓨터 과학에서는 인위적인 지능으로 불린다.
여기서 순서를 조금 변경하고 좀 말이 되게 변경해보자
"앱을 디자인 하면, 찾을 수 없는 진리를 찾는 것을 컴퓨터는 할 수 있다. 이런 걸 컴퓨터 과학에서 인위적인 지능 분야(가지)라고 불린다."
말이 되긴 된다.
근데 정확히 해석을 했을 까? 한번 구글 번역기를 통해서 Ground Truth를 보자.
"컴퓨터를 프로그래밍 할 수 있는 응용 프로그램을 설계하는 것은 인공 지능 (AI)이라는 컴퓨터 과학 분야에서 오랫동안 구해온 기술입니다"
완전 틀렸다.
그러면 어떤게 틀린 건 지 생각해보자.
일단 다른 점을 보자.
1. {grail}를 {기술} 이라는 단어로 선택했다.
2. {designing}, {applications}를 {응용 프로그램을 설계}로 바꿨다.
3. 내가 찍을 땐 {긴}을 버렸는데 {오랫동안 구해온}으로 번역했다.
4. that {그}를 단어로 번역하지 않고 that 앞뒤로 설명하는 것으로 간주했다.
5. the {그}를 버렸다.

그럼 원래의 bag of words를 개선 해보자.
{응용 프로그래밍 설계}, {앞 뒤 설명문}, {할 수 있다}, {프로그램}, {컴퓨터}, {는}, {하나}, {긴}, {찾는}, {기술}, {의}, {가지}, {의}, {컴퓨터}, {과학}, {부르다}, {인위적}, {지능}
ground truth를 봤지만 정확히는 기억이 안난다.
이게 인간의 '미' 이다.
정확히 기억이 않나니 똑같은 방법으로 찍어 본다.
그래도 틀렸으면 반복하다.
너무 많이 하다 보면 당연히 문장이 메모리에 박힌다.
하지만 동시에 해당 bag of words가 나왔을 때 번역하는 방법도 터득이 된다.
또한, 내 눈(pointer)이 자연스럽게 어떤 단어를 먼저 봐야하고 중요시 여겨야 되는지 알게 된다.
이런 방법은 training set(모의고사)이 적은게 아주 적합하다.
또한 batch를 거듭 할 수록,
처리 속도가 자연스럽게 빨라지니 걱정하지 말아라.
오히려 처음에 너무 빨리 하면
뇌에 정확한 train data가 얹히기도 전에 train되니 잘못된 model이 나온다.

Reading code like books

자 이제 메인 토픽이다.
어떻게 하면 코드를 책 처럼 읽을 수 있을 까?
영어를 번역 한 거 처럼 비슷하게 하면 된다.
영어보다 더 좋은 점은 input raw data가 무조건 눈으로 들어오고
아직 발음, 뉘앙스를 아직 무시해도 되는 단계이다.

때 마침 React.js를 공부하고 있다.
아래 예제를 보자.

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

출처: https://ko.reactjs.org/docs/conditional-rendering.html

위와 동일하게 각 단어를 번역을 하자.
여기서 프로그래밍언어 --> 일반언어 번역이
일반언어 --> 일반언어 번역과 다른 점은 번역을 하면서 상식들을 습득해야 한다.
예를 들자면
영어 function은 한국어로 함수로 번역될 수 있는데
프로그래밍 function은 해당 개념을 이해해야 한다.
그러기 때문에 일반 번역보다 다소 오래 걸린다.
(단어외우기 + 코딩 개념탑재에 대해서는 나중에 다른 글을 통해서 다뤄보려고 한다.)

각 단어 따른 해당 개념들을 탑재했다고 가정하자.
그런데도 코드를 일반 언어로 설명을 하라고 하면 어렵다.
대부분 어디서 부터 시작해야 될 지 모르기 때문이다.
책은 왼쪽에서 오른쪽, 그리고 위에서 아래로 읽는다.
하지만 코드는 다르다.
그러면 우리의 눈은 코드를 볼 때 어디를 봐야할까?

React의 경우...

ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

이 부분을 먼저봐야한다. ReactDOM.render() 라는 '키워드'를 통해 컴퓨터가 먼저 보기 때문이다.
그러면 뭐라고 하는 걸 까?
"DOM에가서 root를 찾고 Mailbox라는 걸 찾고 거기에 'unreadMessages={messages}' 라는 정보를 전달해." 이다. 근데 여기서 messages는 어디에 있을까?
바로 직전 라인에 있다.

const messages = ['React', 'Re: React', 'Re:Re: React'];

그러면 하라는 데로 해보자.

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

Mailbox를 찾았다.

function Mailbox(props) {

첫 번째 라인을 보니,
Mailbox라는 함수는 들어오는 input을 함수안에서는 props라고 지정한다.

const unreadMessages = props.unreadMessages;

그리고 input 데이터 중에 unreadMessages를
unreadMessage로 저장한다.
(근데 사실 이 부분은 필요가 없다.)

return(

함수가 궁극적으로 return하는 데이터는

<div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>

이다.

<h1>Hello!</h1>

좀 크게
Hello!라는 걸 h1 크키로 출력하고

{unreadMessages.length > 0 &&

만약 unreadMessages의 길이가 0보다 크다면

		<h2>
          You have {unreadMessages.length} unread messages.
        </h2>

"You have" + "unreadMessages의 길이" + "unread messages."를 h2크기로 출력해라.

Ground truth:
없다...
정확히 말하면 프로그램이 출력물은 있다.

하지만 우리가 원하는 건 코드 --> 일반 언어 이다.
내가 아직 해당 기능을 하는 앱의 존재를 모를수도 있다.
여튼 구글번역기 처럼 코드번역기가 아직까지 존재하지 않는다.
있었으면 좋겠다.
그러면 모든 컴싸 학생이 비약적으로 학습 능력이 올라 것 이다.

비록 Ground Truth가 없겠지만
자신만의 Ground Truth를 만들 수 있다.
코드를 이리저리 움직여 보고 생각했던대로 움직이면
보통 당신이 찍은 code to plain language가 맞을 것 이다.
이렇게 반복하다 보면 어느 순간 코드를 쉽게 책 읽듯이 볼 수 있을 것 이다.

하나 더 연습 해보자,
이번에는 어딜 먼져 봐야하고 모르는 것을 Googling 하고 노트를 하는 것 이다.

class NameForm extends React.Component { //#3
  constructor(props) {
    super(props);
    this.state = {value: ''}; //#8

    this.handleChange = this.handleChange.bind(this); //# Googling
    this.handleSubmit = this.handleSubmit.bind(this); //# Googling
  }

  handleChange(event) { //#10
    this.setState({value: event.target.value}); //Googling
  }

  handleSubmit(event) { //#6
    alert('A name was submitted: ' + this.state.value); //#7
    event.preventDefault(); //Googling
  }

  render() { //#4
    return (
      <form onSubmit={this.handleSubmit}> //#5
        <label>
          Name:
          <input type="text" value={this.state.value} onChange=    {this.handleChange} /> //#9, Googling
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

ReactDOM.render( // #1
  <NameForm />, // #2
  document.getElementById('root')
);

출처: https://ko.reactjs.org/docs/forms.html
만약 순서대로 따라가고 Googling 하면 이런 노트가 나와야 한다.

  1. event.target.value는 event를 생기게 만든 것의 value를 가져다 준다.
  2. onChange가 없으면 input이 바뀔 수 없다. 그래서 꼭 필요하다. 그래서, submit handler 하나가 아닌 handleChange(), handleSubmit() 두개 나오는 것 이다.
  3. .bind(this)는 function의 this scope을 class로 바꿔줌
  4. event.preventDefault()는 event가 실행하고 default로 지정된 event를 못하게 한다 이 경우에는 submit이후 발생하는 redirection을 방지한다.

하나만 더 해보자.

import React, { useRef, useState } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function App() {
  const [inputs, setInputs] = useState({ // #5 useState를 통해 현재 state과 state를 바꿀 수 있는 setInputs를 받아온다. 그리고 기본 state는 아래와 같다.
    username: '', // # 6
    email: '' // # 6
  });
  const { username, email } = inputs; // #4 username은 inputs중 하나이다.
  const onChange = e => {// #9 e input을 받는 arrow 함수인데  e안에 있는 target을 받아 두가지 variable을 받는다. 바로 name과 value이다. 그리고 그것을 state을 바꿔주는 setInputs이용 하여 state를 바꿔준다. 기본적인 dictionary형태는 inputs과 동일하게 한다. 그리고 event를 발생시킨 name을 value로 업데이트 한다.
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  };
  const [users, setUsers] = useState([ // #17 uses 아래와 같은 dictionary 형태로 기본 state를 갖는다.
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com'
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com'
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com'
    }
  ]);

  const nextId = useRef(4); // #13 useRef()를 썼다. 근데 적절하게 쓴지는 의문이다. 그냥 '4'를 .current에 저장하기 위해서 썼다.
  const onCreate = () => { // #11 
    const user = {
      id: nextId.current, #12 nextId가 어떻게 형성됐는지 알아보자
      username,
      email
    };
    setUsers(users.concat(user)); // #14.concat을 통해 추가해준다.

    setInputs({  // #15 다시 state을 원상복귀 시킨다.
      username: '',
      email: ''
    });
    nextId.current += 1; // #16 그리고 nextId.current를 1를 더해서 업데이트 한다.
  };
  return ( #1
    <>
      <CreateUser // #2 CreateUser라는 component에 properties를 보내준다.
        username={username} // #3 무슨 property를 보냈는지 살펴보자
        email={email} // #7 email도 username가 동일한 곳에서 왔다.
        onChange={onChange} // #8 onChange도 살펴보자 보통 onChange는 input text에서 저장하기전에 쓰고 있는 걸 보여지게 하는 걸 생각하고 살펴보자.
        onCreate={onCreate} // #10 onCreate도 살펴보자 보통 onCreate은 새로운 걸 추가 시킬 때 쓴다는 것 생각하고 보자
      />
      <UserList users={users} /> // #16 users를 찾아보자
    </>
  );
}

export default App; // #17 App을 다른 곳에서도 접근할 수 있게 내보낸다.

출처: https://react.vlpt.us/basic/13-array-insert.html

어느정도 경험치가 있는 개발자도
이렇게 코드 한줄, 한줄 "번역"을 하게 되면
머리속에서 깔끔하게 정리 안됐던게
깔끔하게 정리 된다.


2022.04.11 좋은 예시

https://devlopsquare.tistory.com/81

profile
벨로그에 생각을 임시로 저장합니다. 틀린건 틀렸다고 해주세요 :) 그래야 논리 학습이 강화됩니다.

0개의 댓글