이번 강의는 아두이노와 라즈베리파이 비교, 라즈베리파이에 OS(라즈비안) 설치, **라즈베리파이 설정(인터넷, 한글)**에 대한 내용을 다루고 있습니다.
아두이노와 라즈베리파이 비교
아두이노와 라즈베리파이는 서로의 연관검색어 상단에서 볼 수 있는 만큼 유사한점이 많지만 다른점이 분명히 있습니다. 아두이노와 라즈베리파이의 다른점에 대해 알아보고 언제 어떤것을 사용하는것이 효율적일지 알아봅니다.
라즈베리파이에 OS(라즈비안) 설치
라즈베리파이에 라즈비안을 설치합니다. 설치 시 NOOBS 프로그램을 통해 더욱 간편하게 OS를 설치할 수 있습니다.
라즈베리파이 설정(인터넷, 한글)
라즈베리파이3는 라즈베리파이2와 달리 무선 LAN 카드를 내장하고 있기 때문에 기존 모델과 달리 Wifi 동글을 사용하지 않아도 Wifi 사용이 가능합니다. 간편하게 GUI 상에서 Wifi 설정하는 법을 알아보고 학교와 같은 고정IP를 사용해야 하는 경우에도 대비해 고정IP 설정하는 법을 알아봅니다.
추가적으로 라즈비안을 처음 설치했을 경우 한글폰트가 설치되어있지 않기 때문에 한글 폰트 설치 및 언어 설정을 합니다.
오랜만에 포스팅을 하는 것 같습니다. 그간 회사에서 운이 좋게 3주간 보조 강사로 강의를 할 수 있는 기회를 얻어 강의를 하고 있습니다. 신기하게도 제가 졸업한 인천대학교에서 강의를 하고있습니다.
강의 주제는 **라즈베리파이에 음성인식 서비스(STT, TTS)와 GPIO를 활용해 나만의 비서**를 만들기 입니다. 라즈베리파이 같은 경우 이전에 친구들과 스마트미러를 만들며 사용했던 경험이 있었기에 낯설지 않았지만, 강의를 준비하고 강의자료를 만들면서 생각보다 여러가지로 고난을 겪었습니다…
단순히 발표자료가 아닌 누군가에게 지식을 잘 전달할 수 있도록 만들어야 하다보니 생각보다 만드는게 쉽지 않았습니다. 그래도 혹시나 필요하신 분이 있을까 하여 만들었던 강의자료를 공개합니다. 파이썬 관련된 부분은 책을 많이 참고하였기에 생략하고 라즈베리파이와 GPIO, 그리고 음성인식 프레임워크로 사용했던 Jasper에 대한 강의자료를 업로드하겠습니다. 다음은 강의 목차입니다.
- 라즈베리파이 강의 목차 -
라즈베리파이 OS 설치 & 설정
Unix / Linux 소개
사용자 관리, 원격접속 vi 편집기
bash 설정, vim 설정, gist 사용하기
GPIO, LED, BreadBoard
Extra GPIO
Jasper
앞으로 강의에 대한 **간단한 내용정리**와 사용한 **pdf 파일**을 업로드하도록 하겠습니다.
JavaScript **Class**는 ECMAScript 6을 통해 소개되었습니다. ES6의 Class는 기존 prototype 기반의 상속을 보다 명료하게 사용할 수 있도록 문법을 제공합니다. 이를 Syntatic Sugar라고 부르기도 합니다.
Syntatic Sugar : 읽고 표현하는것을 더 쉽게 하기 위해서 고안된 프로그래밍 언어 문법을 말합니다.
JavaScript를 ES6를 통해 처음 접하시는 분들은 알아두셔야할 것이 JavaScript의 Class는 다른 객체지향 언어(C++, C#, Java, Python, Ruby 등…)에서 사용되는 Class 문법과는 다르다는 것입니다. JavaScript에는 Class라는 개념이 없습니다. Class가 없기 때문에 기본적으로 Class 기반의 상속도 불가능합니다. 대신 다른 언어에는 존재하지 않는 프로토타입(Prototype)이라는 것이 존재합니다. JavaScript는 이 prototype을 기반으로 상속을 흉내내도록 구현해 사용합니다. Prototype을 처음 접하시는 분은 “Prototype 이제는 이해하자”를 참고하시면 도움이 될것같습니다.
Class 정의
JavaScript에서 Class는 사실 함수입니다. 함수를 함수 선언과 함수 표현식으로 정의할 수 있듯이 class 문법도 class 선언과 class 표현식 두가지 방법으로 정의가 가능합니다.
JavaScript 엔진은 function 키워드를 만나면 Function 오브젝트를 생성하듯, class 키워드를 만나면 Class 오브젝트를 생성합니다. class는 클래스를 선언하는 키워드이고 Class 오브젝트는 엔진이 class 키워드로 생성한 오브젝트입니다.
Class 선언
함수 선언과 달리 클래스 선언은 호이스팅이 일어나지 않기 때문에, 클래스를 사용하기 위해서는 먼저 선언을 해야합니다. 그렇지 않으면 ReferenceError 가 발생합니다.
say() { console.log('My name is ' + this.name); } }
Class 표현식
Class 표현식은 이름을 가질 수도 있고 갖지 않을 수도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
const People = classPeople{ constructor(name) { this.name = name; }
say() { console.log('My name is ' + this.name); } }
const People = class{ constructor(name) { this.name = name; }
say() { console.log('My name is ' + this.name); } }
constructor
constructor는 클래스 인스턴스를 생성하고 생성한 인스턴스를 초기화하는 역할을 합니다. new People() 코드를 실행하면 People.prototype.constructor가 호출됩니다. 이를 default constructor라고 하며 constructor가 없으면 인스턴스를 생성할 수 없습니다.
1
const people = new People('KimJongMin');
new People(‘KimJongMin’)을 실행하면 People 클래스에 작성한 constructor가 자동으로 호출되고 파라미터 값으로 ‘KimJongMin’을 넘겨 줍니다.
new 연산자가 인스턴스를 생성하는 것처럼 보이지만, 사실 new 연산자는 constructor를 호출하면서 파라미터를 넘겨주는 역할만 합니다. 호출된 constructor가 인스턴스를 생성하여 반환하면 new 연산자가 받아 new를 실행한 곳으로 반환합니다. 과정은 다음과 같습니다.
new People(‘KimJongMin’)을 실행
new 연산자가 constructor를 호출하면서 파라미터 전달
constructor에 작성한 코드를 실행하기 전에 빈 Object 를 생성
constructor 코드를 실행
생성한 Object(인스턴스)에 property 할당 (인스턴스를 먼저 생성했기 때문에 this로 Object 참조 가능
생성한 Object 반환
다음은 생성된 인스턴스의 구조입니다.
1
console.dir(people);
people 인스턴스의 **_proto_**는 People Class 오브젝트와 함께 생성된 Prototype object를 가리키고 있습니다. 결국 Class 문법을 이용한 코드를 prototype 기반의 코드로 변경하면 다음과 같습니다.
1 2 3 4 5 6 7
functionPeople(name) { this.name = name; }
People.prototype.say = function () { console.log('My name is ' + this.name); };
Prototype 기반 상속(ES5)과 Class 기반 상속(ES6) 비교
먼저 ES5에서 Prototype을 사용하여 상속을 구현하는 방법을 살펴보고, 그 후 ES6에서 Class로 상속을 구현하는 형태를 보겠습니다.
new Lion()을 실행하면 Lion()이 호출되고, default constructor를 호출합니다. 그래서 Lion()을 생성자(constructor) 함수라고 합니다.
생성자 함수가 있으면 Cat.prototype.speak와 같이 prototype에 메서드를 연결한 코드가 있습니다. 이와 같이 prototype에 작성하지 않으면 각각의 인스턴스에 메서드가 생성되게 됩니다. 이 형태가 ES5에서 인스턴스를 구현하는 기본 형태 입니다.
Object.create()를 통해 Cat.prototype에 연결된 메서드를 Lion.prototype.__proto__에 첨부합니다. Lion.prototype에는 constructor가 연결되어 있는데 prototype을 재 할당했기 때문에 지워진 constructor를 다시 할당해 줍니다.
ES6에서는 extends 키워드로 상속을 구현합니다. Cat 클래스를 상속받은 Lion 클래스의 구조는 다음과 같습니다.
위의 prototype을 통해 상속을 구현한 Lion 생성자 함수의 구조와 비교했을때 일치합니다. 추가적으로 new Lion(‘Samba’) 를 실행하면 다음의 과정을 거치게됩니다.
Lion 클래스의 constructor를 호출
Lion 클래스에 constructor를 작성하지 않았기 때문에 슈퍼 클래스의(Cat) constructor가 호출됨 (내부적으로 프로토타입 체인으로 인해)
슈퍼 클래스의 constructor에서 this는 현재의 인스턴스를 참조하므로 인스턴스의 name 프로퍼티에 파라미터로 전달받은 값을 설정
생성한 인스턴스를 lion에 할당
super 키워드
서브 클래스와 슈퍼 클래스에 같은 이름의 메서드가 존재하면 슈퍼 클래스의 메서드는 호출되지 않습니다. 이때 super 키워드를 사용해서 슈퍼 클래스의 메서드를 호출할 수 있습니다. (서브 클래스의 constructor에 super()를 작성하면 슈퍼 클래스의 constructor가 호출됩니다.)
static 키워드
static 키워드는 클래스를 위한 정적(static) 메소드를 정의합니다. 정적 메소드는 prototype에 연결되지 않고 클래스에 직접 연결되기 때문에 클래스의 인스턴스화(instantiating) 없이 호출되며, 클래스의 인스턴스에서는 호출할 수 없습니다. 동일한 클래스 내의 다른 정적 메서드 내에서 정적 메서드를 호출하는 경우 키워드 this를 사용할 수 있다.
정적 메소드는 어플리케이션(application)을 위한 유틸리티(utility) 함수를 생성하는데 주로 사용됩니다.
마치며
ES6의 Class 문법에 대해 정리해 보았다. JavaScript 언어를 약 1년전 Node.js 를 시작하며 처음 접하게 되었는데 사실 그 당시 Prototype과 상속에 대해 크게 다룰일이 없었다. (어쩌면 너무 무지해서 사용 필요성을 느끼지 못했을 수도…) 그 후 Node.js 버전을 올리고 ES6를 공부하며 Class 문법을 접하게 되었는데 JavaScript의 Prototype에 대한 이해와 지식이 부족하다 보니 이전에 공부했던 C++과 Java의 Class 처럼 이해했던 것 같다. 그래도 그 후 Prototype과 더불이 Class까지 공부하며 지금은 어느정도 이해하게 된것 같다. 결론은… 역시나 JavaScript에서 Prototype을 이해하는건 중요한것 같다.
자바 스크립트에서 함수는 객체이기 때문에 프로퍼티를 가질 수 있습니다. 그리고 언제든지 함수에 사용자 정의 프로퍼티를 추가할 수도 있습니다. 함수에 프로퍼티를 추가하여 결과(반환 값)을 캐시하면 다음 호출 시점에 복잡한 연산을 반복하지 않을 수 있습니다. 이런 활용 방법을 **메모이제이션 패턴**이라고 합니다.
다음 코드에서는 myFunc 함수에 cache 프로퍼티를 생성합니다. 이 프로퍼티는 일반적인 프로퍼티처럼 myFunc.cache와 같은 형태로 접근할 수 있습니다. cache 프로퍼티는 함수로 전달된 param 매개변수를 키로 사용해서 계산의 결과를 값으로 가지는 객체(해시)입니다. 결과 값은 필요에 따라 복잡한 데이터 구조로 저장할 수도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
var myFunc = function (param) { if (!myFunc.cache[param]) { var result = {}; // ... // 비용이 많이 드는 수행 후 result에 결과 저장 // ... myFunc.cache[param] = result; } return myFunc.cache[param]; };
// 캐시 저장공간 myFunc.cache = {};
메모이제이션 적용하기
메모이제이션을 공부한 후 현재 진행중인 에밀리(개인 프로젝트)에 적용해 보았습니다. 어느 부분에 적용했는지 적용 전과 적용 후 얼마나 효율이 올라갔는지를 알아보겠습니다.
수정할 부분
메모이제이션 패턴을 적용할 코드가 현재 하고 있는 기능은 다음과 같습니다. 사용자의 요청(학생식당, 카페테리아, 사범대식당, 기숙사식당, 교직원식당)에 따라 미리 크롤링 후 DB에 저장되어 있는 데이터(메뉴)를 가져와 응답합니다.
메모이제이션 패턴을 사용함으로써 얻을 수 있는 이점은 비용이 많이 드는 결과를 캐싱하고 그 이후에 재사용함으로써 비용을 줄일 수 있다는 것입니다. 현재 코드에서 비용이 많이 드는 작업은 DB를 조회하는 부분입니다. DB에는 다음과 같이 미리 크롤링한 데이터가 날짜별로 저장되어 있습니다.
사용자의 요청에 따라 해당 날짜의 데이터를 조회한 후 사용자가 요청한 식당에 맞는 데이터를 결과로 반환합니다. 그렇기 때문에 해당 날짜의 최초 요청이 이루어진 후 그 하루 동안에는 계속해서 DB에 같은 쿼리를 통해 같은 결과를 얻게 됩니다. 이 부분이 비용이 많이 드는 작업이기 때문에 메모이제이션 패턴을 통해 개선해보았습니다.
메모이제이션 적용 전
먼저 메모이제이션 패턴을 적용하기 전 코드입니다. menuHandler 클래스의 getMenu 함수는 menuService를 통해 DB에서 해당 날짜의 식당 메뉴를 가져와 매개변수로 전달받은 식당의 이름을 사용하여 결과로 반환하는 역할을 합니다.
classmenuHandler{ staticgetMenu(place) { returnnewPromise((_s, _f) => { const today = newDate().yyyymmdd(); let menu = '';
menuService.show(today) .then(menuList => { if (!menuList) { menu = '데이터가 없습니다. 관리자에게 문의해주세요'; }
switch (place) { case'학생식당': menu = menuList.student.join('\n\n'); break; case'카페테리아': menu = menuList.cafeteria.join('\n\n'); break; case'사범대식당': menu = menuList.education.join('\n\n'); break; case'기숙사식당': menu = menuList.dormitory.join('\n\n'); break; case'교직원식당': menu = menuList.staff.join('\n\n'); break; }
_s(menu); }) .catch(err => { _f(err); }); }); } }
module.exports = menuHandler;
3. 메모이제이션 적용 후
메모이제이션 패턴을 적용 한 후 코드입니다. setCache, getCache, pickMenu 함수가 추가되었고, getMenu 함수의 코드도 조금 변경되었습니다. 기존 코드의 getMenu 함수에서는 바로 DB를 조회하여 결과를 반환하였지만, 변경된 코드에서는 getCache 함수를 통해 캐시에 저장된 데이터가 있는지 확인 후 분기하여 처리합니다.
메모이제이션 패턴을 적용하기 전과 적용한 후의 성능 차이는 아래 보이는것처럼 눈에 띄게 차이가 납니다. 메모이제이션 패턴을 적용한 코드는 첫번째 요청(캐싱 하기 전)때는 메모이제이션 패턴 적용 전과 응답속도가 비슷하지만 그 이후의 응답은 캐싱된 데이터를 이용하기 때문에 비교될 정도로 빨라졌습니다.
마치며
메모이제이션을 적용하기 전과 후 모두 같은 기능을 결과를 만들어내는 코드이지만 코드를 작성하는 방법에 따라 더욱 더 빠른 효율적인 서비스를 만들 수 있다는 것을 느끼게 되었습니다. 현재 제 상황에서는 캐싱된 데이터도 하루가 지나게되면 쓰이지 않고 계속 메모리에 남아있게 되는데 그 부분에 대한 처리를 추가해야할것 같습니다. 이번에 적용한 코드 뿐만 아니라 아직 프로잭트 내에 메모이제이션 패턴을 적용할 수 있는 부분이 더 있습니다. 앞으로 디자인패턴 공부를 계속하여 새로운 패턴들을 적용시키며 리팩토링을 해야겠습니다.
자바스크립트에서는 비동기 프로그래밍 해결을 위해 하나의 패턴으로 콜백을 사용했다. 그러나 콜백 패턴은 비동기 처리 중 발생한 오류를 예외 처리하기 힘들고 여러 개의 비동기 로직을 한꺼번에 처리하는 데도 한계가 있다. 즉 콜백 패턴은 그다지 유용한 패턴이 아니다. 이때 비동기 프로그래밍을 위한 또 다른 패턴으로 Promise가 등장했다.
**Promise**는 비동기 처리 로직을 추상화한 객체와 그것을 조작하는 방식을 말한다. Promise를 지원하는 함수는 비동기 처리 로직을 추상화한 promise 객체를 반환 한다. 그리고 객체를 변수에 대입하고 성공 시 동작할 함수와 실패 시 동작할 함수 를 등록해 사용한다.
함수를 작성하는 방법은 promise 객체의 인터페이스에 의존 한다. 즉, promise 객체에서 제공하는 메서드만 사용해야 하므로 전통적인 콜백 패턴처럼 인자가 자유롭게 결정되는 게 아니라 같은 방식으로 통일된다. Promise 라고 부르는 하나의 인터페이스를 이용해 다양한 비동기 처리 문제를 해결할 수 있다. 복잡한 비동기 처리를 쉽게 패턴화할 수 있다는 뜻이다. 이것이 Promise의 역할이며 Promise를 사용하는 많은 이유 중 하나다.
Promise 사용법
Promise는 new 연산자를 선언하여 Promise 인스턴스 객체를 생성한다.
1 2 3
const promise = newPromise(function(resolve, reject) { // 비동기 처리 로직 후 resolve 또는 reject를 호출 });
new 연산자로 생성된 Promise 인스턴스 객체에는 성공(resolve), 실패(reject)했을 때 호출될 콜백 함수를 등록할 수 있는 Promise.then()이라고 하는 인스턴스 메서드가 있다.
1
promise.then(onFulfilled, onRejected)
성공했을 때는 onFulfilled가 호출되고 실패했을 때는 onRejected가 호출된다. promise.then()으로 성공 혹은 실패 시의 동작을 동시에 등록할 수 있다. 만약 오류 처리만 한다면 promise.then(undefined, onRejected)와 같은 의미인 promise.catch(onRejected)를 사용하면 된다.
1
promise.catch(onRejected)
Promise 상태
생성자 함수를 new 연산하여 생성된 Promise 인스턴스 객체에는 3가지 상태가 존재한다. promise 객체는 Pending 상태로 시작해 Fulfilled나 Rejected 상태가 되면 다시는 변화하지 않는다. (Event 리스너와는 다르게 then()으로 등록된 콜백함수는 한 번만 호출된다.)
Pending : 성공도 실패도 아닌 상태, Promise 인스턴스 객체가 생성된 초기상태
Fulfilled : 성공(resolve)했을 때의 상태, onFulfilled가 호출된다.
Rejected : 실패(reject))했을 때의 상태, onRejected 호출된다.
Promise.resolve, Promise.reject
Promise의 정적 메서드인 **Promise.resolve()**를 사용하면 new Promise() 구문을 단축해 표기할 수 있다. Promise.resolve()는 Fulfilled 상태인 promise 객체를 반환한다. 또한, Promise. resolve()는 thenable 객체를 promise 객체로 변환할 수 있다. 이것은 Promise.resolve()의 중요한 특징 중 하나다.
thenable은 ES6 Promises 사양에 정의된 개념이다. then()을 가진 객체 즉, 유사 promise 객체를 의미한다. length 프로퍼티를 갖고 있지만, 배열이 아닌 유사 배열 객체 Array-like Object와 같다. Promise.resolve()는 thenable 객체의 then() 이 Promise의 then()과 같은 동작을 할 것이라 기대하고 promise 객체로 변환한다.
**Promise.reject()**도 promise 객체를 반환한다. 따라서 에러 객체와 함께 catch()를 이용해 등록한 콜백 함수가 호출된다.
Promise.prototype.then
Promise에서는 메서드를 체인하여 코드를 작성할 수 있다. then()은 콜백 함수를 동록하기만 하는것이 아니라 콜백에서 반환된 값을 기준으로 새로운 promise 객체를 생성하여 전달하는 기능도 갖고 있다.
Promise.all, Promise.race
**Promise.all()**은 Promise 객체를 배열로 전달받고 객체의 상태가 모두 Fulfilled 됐을 때 then()으로 등록한 함수를 호출한다. **Promise.race()**는 Promise.all()과 마찬가지로 promise 객체를 배열로 전달한다. Promise.all()과 달리 전달한 객체의 상태가 모두 Fulfilled가 될 때까지 기다리지 않고 전달한 객체 중 하나만 완료(Fulfilled, Rejected)되어도 다음 동작으로 넘어간다. Promise.race는 먼저 완료된 promise 객체가 있더라도 다른 promise 객체를 취소하지 않는다. (ES6 Promise 사양에는 취소라는 개념이 없다.)
Promise 특징
Promise는 항상 비동기로 처리된다.
Promise.resolve()나 resolve()를 사용하면 promise 객체는 바로 Fulfilled 상태가 되기 때문에 then()으로 등록한 콜백 함수가 동기적으로 호출될 것이라 생각할 수 있다. 하지만 실제로는 then()으로 등록한 콜백 함수는 비동기적으로 호출된다. 동기적으로 처리 가능한 상황에서도 비동기적으로 처리하는 이유는 동기와 비동기가 혼재될때 발생하는 문제를 막기 위함이다.
새로운 promise 객체를 반환하는 then
promise.then(), catch()는 최초의 promise 객체에 메서드를 체인하는 것처럼 보이지만 실제로는 then()과 catch()는 새로운 promise 객체를 생성해 반환한다. Promise.all()과 Promise.race() 또한 새로운 promise 객체를 생성해 반환한다.
콜백-헬과 무관한 Promise
Promise는 callback-hell 을 해결할수는 없고 완화할 수 있을 뿐이다. 완화할 수 있는 이유는 단일 인터페이스와 명확한 비동기 시점 표현, 강력한 에러 처리 메커니즘 때문이다. 이는 비동기 처리 자체를 손쉽게 다룰 수 있도록 하는 것이므로 callback-hell 을 해결하는 방법으로 여기는건 바람직하지 않다.