전체 소스코드 깃허브 링크
https://github.com/heftyCornerstone/number-remembering-game/tree/master
class genNumber{
constructor(randomNum) {
this._randomNum = undefined;
}
setRandomNum(){
this.randomNum = Math.floor(Math.random()*1000);
}
getRandomNum(){
return this.randomNum
}
}
function eventListenerSetter(){
const gameNumber = new genNumber();
//입력 가능시점, 불가능 시점의 버튼 상호작용과 안내문 관리
function inputModeSwitcher(state){
if(state=='ready'){
enteredNum.disabled=true;
appearingNum.innerHTML = 'Ready...';
enteredNum.placeholder='시작해주세요';
gameStartBtn.disabled = false;
submitNumBtn.disabled = true;
enteredNum.value = '';
} else if('go'){
enteredNum.disabled=false;
appearingNum.innerHTML = '이제 그만!';
enteredNum.placeholder='숫자를 입력해주세요';
submitNumBtn.disabled = false;
} else{
console.log('inputModeSwitcher : 잘못된 state값이 입력되었습니다');
}
}
//랜덤한 숫자를 노출하는 동안 버튼 상호작용과 안내문 관리
function duringCounting(){
enteredNum.placeholder = '3초전';
showAnswer.innerHTML = '정답이 여기에 표시됩니다';
gameStartBtn.disabled = true;
}
//랜덤한 숫자 노출하기
function showNumForSecs(sec){
gameNumber.setRandomNum();
appearingNum.innerHTML = gameNumber.getRandomNum();
duringCounting();
setTimeout(()=>{inputModeSwitcher('go')},sec*1000);
}
//유저 입력 정답 여부 확인
function onSubmit(e){
const entered = Number(e.target.enteredNum.value);
const answer = gameNumber.getRandomNum();
(entered===answer) ? showAnswer.innerHTML='정답입니다!' : showAnswer.innerHTML=`오답입니다. 정답은 ${answer}입니다.`;
inputModeSwitcher('ready');
}
//이벤트리스너 할당하기
gameStartBtn.addEventListener('click', ()=>{showNumForSecs(3)});
form.addEventListener('submit', (e)=>{e.preventDefault(); onSubmit(e);});
}
eventListenerSetter();
- genNumber 클래스 : 랜덤한 숫자를 생성하고, 생성한 숫자에 접근한다.
- eventListenerSetter 함수 : 버튼에 이벤트리스너를 할당하는 과정 전체를
함수 스코프로 감싼다.- eventListenerSetter(); : 함수를 호출하여 이벤트리스너 할당을 수행한다.
그냥 아래처럼 구현하면 안될까요?
//generateNum에서 랜덤한 숫자를 할당하면, 이후 onSubmit에서 유저의 입력과 비교한다.
let randomNum = undefined;
function onSubmit(e){
//유저의 입력과 randomNum을 비교해 정답 여부를 검사한다
}
function generateNum(){
//랜덤한 숫자를 할당, 그리고 유저에게 노출한다
}
form.addEventListener('submit', (e)=>{onSubmit(e)});
gameStartBtn.addEventListener('click', generateNum);
다수의 함수간에 공유하는 값이 하나 있다면,
전역변수를 이용하여 할당하고픈 생각이 쉽게 들 수 있습니다.
그러나 전역변수는 되도록이면 사용을 자제하여야 해요.
그 이유는 크게 두가지가 있습니다.
전역변수의 스코프는 어디서든 접근 가능한 글로벌 스코프입니다.
만일 우리의 코드가 길고 복잡해진다면, 아차하는 사이에 덮어 씌이기 십상이에요.
그러므로 아무도 모르는 사이에 나 혹은 나의 팀원이 버그를 만들게 될지도 몰라요.
코드를 유지 및 보수하기가 까다로워지는거죠.
그리고 전역변수는 프로그램이 동작하기 시작할 때부터 마침내 종료가 될 때까지 메모리 공간을 차지합니다(모든 곳에서 접근 가능해야 하니까). 따라서 무분별한 전역변수 사용은 메모리 관리 차원에서 비효율적이기도 합니다.
숫자 기억 게임이라는 건 요구사항에 따르자면
화면에 나오는 랜덤한 숫자를 기억하였다가 맞추는 게임입니다.
하지만 꼭 규칙에 따라서 플레이를 해야만 하는걸까요?
아무도 규칙을 지키라며 강제하지 않는데도...?
우리가 아주 삐딱한 사람이라고 생각하고 조금 다른 시선으로 프로그램을 바라보면
새로운 사실을 하나 깨달을 수 있어요.
사유재산인 줄 알았더니 공공재였어!
클라이언트측으로 전송한 파일은
개발자 도구를 이용하면 클라이언트가 얼마든지 열람할 수 있습니다.
따라서...
기억 없는 기억 게임?
개발자도구 콘솔창에서 아주 쉽게 접근 가능합니다.
어휴, 구현하던 것이 숫자 기억하기 게임이어서 다행이에요.
만일 뱅킹 어플이었다면... 그 다음은 상상하기 싫습니다.
그래서 랜덤한 숫자 생성하기와 접근하기는 class에게 맡기고,
이벤트리스너를 할당하는 과정 전체는 함수 스코프로 감쌌습니다.
먼저, class 내부의 private 프로퍼티는 클래스 내부의 메소드를 통해서만 접근 가능해요.
class genNumber{ constructor(randomNum) { this._randomNum = undefined; } ... }
소스코드를 잘 보면 randomNum이 언더바로 시작하는 것을 볼 수 있어요.
프로퍼티를 private하게 설정하겠다는 뜻이에요.
그리고 genNumber의 인스턴스 gameNumber는
eventListenerSetter 함수 내부에서 생성이 되어서 함수 스코프를 지니게 돼요.
function eventListenerSetter(){ const gameNumber = new genNumber(); ... }
함수 스코프 내부는 함수 외부에서 접근할 수 없기 때문에
클라이언트가 인스턴스 gameNumber에 접근할 수 없게 될거예요.
과제의 구현 요구사항 자체는 까다롭지 않았지만
다시 한번 클라이언트측 보안을 생각하게 된 과제였다.
문서 작성 측면에서는 내가 들고 있는 지식과 생각을 잘 녹여내기 위해서
다른 사람에게 설명하는 것처럼 TIL을 구성하여 보았다.
제품 설명문처럼 딱딱하게 작성하는 것보다 더 구조적으로 지식정리를 할 수 있었다.
앞으로도 이렇게 작성해 보아야겠다.
보안 문제를 해결하기 위해 적용한 아이디어가 좋은 아이디어였을지도 궁금하지만
무엇보다 내가 작성한 코드가 다른 사람들에게는 어떻게 읽힐지 궁금하다.
과연 나는 가독성이 좋은 코드를 작성하였을까?
언젠가 이에 관해 대화를 나눌 사람이 생긴다면 좋겠다!