Dev-Tino 4주차(2):: JavaScript에서의 코딩 스타일, 코드 리팩토링
리팩토링-화살표 함수를 사용하여.


문체가 있고 그림체가 있듯이 코딩에도 스타일이 있다. 회사마다, 프로젝트마다 자기 나름대로의 코딩 규범이 있고, 오류와 conflict를 막기 위해 우리는 그 코딩 규범을 제대로 지켜야 한다. 그 관점에서 보면 나는 다른 분들에 비해 부족하다. 코딩 규범에 대해 제대로 찾아본 적이 없기 때문이다.
이번 글에서는 JavaScript의 코딩 규칙을 알아보고 그에 맞추어 전에 짜두었던 코드를 수정하고자 한다. 이번 글은 하단의 코딩 규범 글을 참고한다.::
“개발자는 가능한 한 간결하고 읽기 쉽게 코드를 작성해야 합니다.
복잡한 문제를 간결하고 사람이 읽기 쉬운 코드로 작성해 해결하는 것이야말로 진정한 프로그래밍 기술입니다.
좋은 코드 스타일은 이런 기술을 연마하는 데 큰 도움을 줍니다.“
그 글은 추천할만한 코드 규칙으로 다음 12가지를 소개하였다::
1. 매개변수 사이에는 한 칸의 공백을 두기
2. 중괄호는 앞에 공백 한 칸을 두고 같은 줄에 작성
3. 함수 이름과 괄호 및 인자 사이에는 공백 없음
4. 공백 두 칸으로 들여쓰기
5. for/if/while 뒤에 공백 한 칸씩
6. 논리적으로 다른 블록은 한 줄 띄어쓰기
7. 중첩된 호출 앞뒤에 공백 한 칸씩
8. 연산자 앞뒤에 공백 한 칸씩
9. 세미콜론‘;’ 필수
10. 인수 사이에 공백 한 칸
11. 한 줄은 너무 길지 않게
12. else는 줄바꿈 없이
13. 중첩된 호출 앞뒤에 공백 한 칸씩
예를 들어 중괄호 부분이다.
대부분의 자바스크립트 프로젝트에서 여는 중괄호는 새로운 줄이 아닌, 상응하는 키워드와 같은 줄에 작성한다. 여는 중괄호 앞에 공백이 한 칸 정도 있다면 더 좋다. 가독성이 좋아진다. 이를테면 이런 느낌이다.
var insertBtn = function(someElement){ 좋지 않은 예시. 가독성 나쁨. var editSpan = document.createElement("SPAN"); var editTxt = document.createTextNode("edit"); editSpan.className = "edit"; editSpan.appendChild(editTxt); someElement.appendChild(editSpan); var closeSpan = document.createElement("SPAN"); var closeTxt = document.createTextNode("\u00D7"); closeSpan.className = "close" closeSpan.appendChild(closeTxt); someElement.appendChild(closeSpan); } var insertBtn = function (someElement) { 좋은 예시. 한 칸 띄어씀. //코드 내용 작성... }
기본적으로 위 글은 ‘한 줄에 정리되는 코드가 아니라면 중괄호를 추가하기’를 권장한다.
그러나 한 줄로는 정리가 되지 않는다면 중괄호를 넣으며, 이 때 매개 변수와 중괄호 사이에 띄어쓰기를 한 칸 넣는 것이 가독성이 좋음을 알려주었다. 나는 웬만한 경우에 중괄호를 제대로 삽입하나, 매개 변수와 함수명, 중괄호를무자비하게 붙여둔다는 점이 문제가 되었다. ‘한 줄이 아닐 경우 중괄호를 제대로 삽입하여 정리하되’ ‘코드가 짧다면 한 줄로 정리’해보자.
가로 길이
한글 프로그램을 이용하여 다룰 때에도, 문장이 너무 길어져 가로 창을 넓혀야 할 때가 오면 번잡스럽고 복잡하단 느낌을 받게 된다. 그렇기에 내가 알고 있는 대부분의 문서 작성 프로그램은 화면보다 글이 길어지면 자동으로 줄바꿈해주는 기능을 지원해주고 말이다.
그러나 코딩 에디터의 경우 이런 기능을 제공하지 않는다. 제공해주는 것이 되려 가독성을 해칠 수도 있기 때문이다. 그렇기에 프로그래밍을 작성하는 프로그래머의 경우 스스로 ‘가로 길이가 늘어질 경우 여러 줄로 나눠 작성하는’ 센스를 가져야 한다. 예를 들면 다음과 같다.
let exmStr = `.................................................................................................`; let exmStr2 = ` 적당히 여러줄로 나눠서 작성하는 센스를 가지도록 하자`;
들여쓰기
들여쓰기에는 ‘가로 들여쓰기’ (스페이스 두 개, 혹은 네 개), ‘세로 들여쓰기’(논리 블록 사이에 넣어 코드를 분리해주는 새 줄) 가 있다. 가로 들여쓰기는 ‘블록의 구분’을 이용해 만드는 것으로, ‘스페이스바’ 또는 ‘탭 키’ 를 이용하여 분리한다. 인수의 위치를 여는 괄호와 맞추기 위한 방식이다.
var exmFunc = () => { var x1 = 2; var x2 = 4; return x1 + x2; } //가로 들여쓰기는 인수의 위치를 여는 괄호와 맞추는 등, 가독성을 높이기 위한 방법이다.
세로 들여쓰기는 논리 블록 사이에 넣어 코드를 분리해주기 위해 쓴다.
함수 하나에는 논리 블록 여러 개가 들어갈 수 있다. 변수 선언, 반복문, 리턴문… 맥락이 다른 블록 여러 개가 들어간다면, 변수 선언과 반복문, 리턴문 사이에 세로 들여쓰기를 해주는 빈 줄을 넣어 코드를 분리하는 것이 좋다.
//편집 버튼 함수화. 편집 버튼을 누를 시 작동한다. //세로 띄어쓰기 예시. 변수 선언과 반복문, 리턴문 사이에 세로 띄어쓰기를 넣었다. var editFunc = function (editItem) { editItem.onclick = function () { // <-- var div = this.parentElement; var newTxt = prompt ("todo 리스트 항목을 어떻게 수정할까요?"); // <-- if (newTxt === "") { alert("다시 입력하십시오."); } else { div.textContent = newTxt; insertBtn(div); liveBtn(); } } }
이렇게 여분의 줄을 넣어두면 코드의 가독성이 좋아진다. 읽기 쉬운 코드를 만들기 위해, 세로 들여쓰기 없이 코드를 아홉 줄 이상 연속해서 쓰지 말자.
세미콜론
프로그래밍 언어에서, 기본적으로 세미콜론(;)은 구문의 종료를 나타낸다. 자바와 C++등이 세미콜론을 이렇게 사용하나, 자바스크립트는 세미콜론을 붙여도 되고 붙이지 않아도 된다.
자바 스크립트에 세미콜론을 넣어도 되고 안 넣어도 되는 이유는, 자바 스크립트가 ASI(Automatic Semicolon Insertion)이기 때문이다. 인처 프리터 과정에서 자동으로 구문 구분을 해야 하는 곳을 판단하여 세미콜론을 붙여주는 시스템을 이르는 말이다. 그러나 이 체계의 경우, ‘개발자의 의도와는 다르게 세미콜론이 찍히지 않는 경우’가 발생할 수 있기 때문에 되도록이면 세미콜론을 찍어주는 것이 좋다.
ASI 세미콜론 삽입 규칙은 다음과 같다::
1. 줄바꿈이 되는 행의 마지막에 삽입한다.
2. 행의 마지막이 }이고 줄바꿈이 되어 다음 행이 시작될 때 삽입한다.
3. 파일의 끝이 도달할 때 삽입한다.
4. return이 있는 행의 마지막에 삽입한다.
5. break가 있는 행의 마지막에 삽입한다.
6. throw가 있는 행의 마지막에 삽입한다.
7. continue가 있는 마지막에 삽입한다.
이 세미콜론의 삽입을 통해, 예외 상황(ASI가 세미콜론을 잘못 삽입하는 경우, 에러 발생을 방지하고 통일성 있는 코드를 만들기 위해.)을 대비하고 구문을 구분하여 가독성을 높일 수 있다. (TC39, ECMA 인터내셔널의 ECMA 스크립트 담당 기술 위원회에서도 세미콜론을 사용하는 것을 권장한다.)
이런 경우 오류가 발생한다::
/*오류가 나는 경우*/ //구문을 동일한 라인에 작성하는 경우. //대부분의 경우 ' ';' expected. ' 오류를 띄워준다. console.log("어쩌고1") console.log("어쩌고2") //...
중첩 레벨
중첩 레벨 역시 신경쓰는 것이 좋다. 반복 문을 사용할 때 깊이가 길어지면 continue를 쓰는 식이다.
/*가독성을 높이기 위한 방식*/ function pow(x, n) { if (n < 0) { alert("'n'은 음수가 될 수 없습니다."); } else { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } } //둘은 같은 식으로 작동한다. function pow(x, n) { if (n < 0) { alert("'n'은 음수가 될 수 없습니다."); return; } let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; }
위에서 언급하였듯 회사와 조직마다 스타일 가이드가 다르므로, 이에 대해서는 각 조직의 스타일 가이드를 참고하여 작성하는 것이 제일 좋다.
유명 스타일 가이드로는 다음이 있다::
2. 위 스타일 가이드를 적용하여 리팩토링
나름대로 깔끔하게 코드를 정리하였다고 생각하였으나 여전히 부실하다는 말을 들었다. 코드 중 몇몇을 화살표 함수로 대체하고 내용을 정리하면 좋을 것이라고 말이다. 다음은 저번 내 투두리스트 작업 코드 중 자바스크립트 부분이다. 이 코드를, 위의 스타일 가이드를 적용하여 다시 한 번 정리해보았다.
기존의 코드::
//리스트의 각 아이템에 삭제 버튼을 추가한다.겸사겸사 편집 버튼도 추가했다. var insertBtn = function(someElement){ var editSpan = document.createElement("SPAN"); var editTxt = document.createTextNode("edit"); editSpan.className = "edit"; editSpan.appendChild(editTxt); someElement.appendChild(editSpan); var closeSpan = document.createElement("SPAN"); var closeTxt = document.createTextNode("\u00D7"); closeSpan.className = "close" closeSpan.appendChild(closeTxt); someElement.appendChild(closeSpan); } //삭제 기능 함수화. x 버튼을 누르면 작동한다. var closeFunc = function(closeItem){ closeItem.onclick = function(){ var div = this.parentElement; div.style.display= "none"; } } //편집 버튼 함수화. 편집 버튼을 누를 시 작동한다. var editFunc = function(editItem){ editItem.onclick = function(){ var div = this.parentElement; var newTxt = prompt("todo 리스트 항목을 어떻게 수정할까요?"); if(newTxt ===""){ alert("다시 입력하십시오."); }else{ div.textContent = newTxt; insertBtn(div); liveBtn(); } } } //버튼에 기능 심는 작업 var liveBtn = function(){ for (let closeItem of close) { closeFunc(closeItem); } for(let editItem of edit){ editFunc(editItem); } } //----------------------------------------------- var myNodelist = document.getElementsByTagName("LI"); for(let myNodeListItem of myNodelist){ insertBtn(myNodeListItem); } //삭제 버튼을 클릭하면 현재 리스트 아이템을 숨긴다. var close = document.getElementsByClassName("close"); for(let closeItem of close){ closeFunc(closeItem); } //edit 버튼을 클릭하면 그에 맞는 작업을 한다. var edit = document.getElementsByClassName("edit"); for (let editItem of edit){ editFunc(editItem); } //리스트 아이템을 클릭했을 때 체크 심볼로 바꾼다(더한다). var list = document.querySelector('ul'); list.addEventListener('click', function(ev){ if(ev.target.tagName === 'LI'){ ev.target.classList.toggle('checked'); } }); //----------------------------------------------- //ADD 버튼을 누르면 새 아이템이 추가된다. function newElement(){ var li = document.createElement("li"); var inputValue = document.getElementById("myInput").value; li.appendChild(document.createTextNode(inputValue)); if(inputValue === ''){ alert("You must write something!"); }else{ document.getElementById("myUL").appendChild(li); insertBtn(li); liveBtn(); } document.getElementById("myInput").value=""; } //----------------------------------------------- //사용자 경험을 위해 엔터 키를 눌러도 ADD 버튼을 누른 것과 동일한 효과를 가지도록 수정함. document.addEventListener("keyup",function(event){ if(event.keyCode ===13){ newElement(); } })
다음은 리팩토링한 코드이다::
<script> //리스트의 각 아이템에 삭제 버튼을 추가한다.겸사겸사 편집 버튼도 추가했다. var insertBtn = (someElement) => { var editSpan = document.createElement("SPAN"); var editTxt = document.createTextNode("edit"); var closeSpan = document.createElement("SPAN"); var closeTxt = document.createTextNode("\u00D7"); editSpan.className = "edit" ; editSpan.appendChild(editTxt); someElement.appendChild(editSpan); closeSpan.className = "close" closeSpan.appendChild(closeTxt); someElement.appendChild(closeSpan); } //삭제 기능 함수화. x 버튼을 누르면 작동한다. var closeFunc = (closeItem) => { closeItem.onclick = function () { var div = this.parentElement; div.style.display= "none"; } } //편집 버튼 함수화. 편집 버튼을 누를 시 작동한다. var editFunc = (editItem) => { editItem.onclick = () => { var div = this.parentElement; var newTxt = prompt("todo 리스트 항목을 어떻게 수정할까요?"); if ( newTxt === "" ) { alert("다시 입력하십시오."); } else { div.textContent = newTxt; insertBtn(div); liveBtn(); } } } //버튼에 기능 심는 작업 var liveBtn = () => { for (let closeItem of close) { closeFunc(closeItem); } for (let editItem of edit) { editFunc(editItem); } } //----------------------------------------------- var myNodelist = document.getElementsByTagName("LI"); for ( let myNodeListItem of myNodelist ) { insertBtn(myNodeListItem); } //삭제 버튼을 클릭하면 현재 리스트 아이템을 숨긴다. var close = document.getElementsByClassName("close"); for( let closeItem of close ) { closeFunc(closeItem); } //edit 버튼을 클릭하면 그에 맞는 작업을 한다. var edit = document.getElementsByClassName("edit"); for ( let editItem of edit ) { editFunc(editItem); } //리스트 아이템을 클릭했을 때 체크 심볼로 바꾼다(더한다). var list = document.querySelector('ul'); list.addEventListener('click', (ev) => { if(ev.target.tagName === 'LI') { ev.target.classList.toggle('checked'); } }); //----------------------------------------------- //ADD 버튼을 누르면 새 아이템이 추가된다. var newElement = () => { var li = document.createElement("li"); var inputValue = document.getElementById("myInput").value; li.appendChild(document.createTextNode(inputValue)); if (inputValue === '') { alert("You must write something!"); } else { document.getElementById("myUL").appendChild(li); insertBtn(li); liveBtn(); } document.getElementById("myInput").value = ""; } //----------------------------------------------- //사용자 경험을 위해 엔터 키를 눌러도 ADD 버튼을 누른 것과 동일한 효과를 가지도록 수정함. document.addEventListener( "keyup", (event) => { if(event.keyCode === 13){ newElement(); } }) </script>
1. 화살표 함수의 연습이 미흡하기에, 이것을 수련하기 위해 함수를 화살표 함수로 변경하였다.
2. 가독성을 위하여 코딩 스타일에 따라 몇몇 부분의 띄어쓰기를 추가하였다.
다음은 멘토님의 조언에 따라 챗GPT에게 리팩토링을 부탁하여 만든 코드이다::
이 코드를 기능을 유지하면서 전체적으로 리팩토링하는 것은 좋은 아이디어입니다. 코드를 더 깔끔하게 구조화하고 재사용 가능한 부분을 함수로 추출하여 중복을 줄이는 것이 목표입니다. 또한, HTML과 CSS를 최대한으로 활용하여 JavaScript에서는 필수적인 기능만 처리하도록 만들어 보겠습니다. 여기서 주의할 점은: - 함수 이름을 명확하게 지으면서도 일관성 있게 정의합니다. - `onclick` 속성을 JavaScript의 `addEventListener`로 대체하여 HTML과 JavaScript의 분리를 유지합니다. - CSS를 잘 활용하여 스타일링을 더 깔끔하게 만듭니다. 다음은 리팩토링한 코드입니다: ```html <!DOCTYPE html> <html lang="kr"> <head> <meta charset="UTF-8"> <title>기본적인 To-do list</title> <style> * { box-sizing: border-box; } body { font-family: Arial, sans-serif; background-color: #f2f2f2; } .header { background-color: #0f1741; color: #eee; text-align: center; padding: 30px 0; } .header h2 { margin: 0; font-size: 28px; } #myDIV { display: flex; align-items: center; justify-content: center; margin-bottom: 20px; } #myInput { flex: 1; padding: 10px; font-size: 18px; border: none; border-radius: 5px 0 0 5px; } .addBtn { padding: 10px 20px; background-color: #d9d9d9; border: none; cursor: pointer; font-size: 18px; border-radius: 0 5px 5px 0; transition: background-color 0.3s; } .addBtn:hover { background-color: #bbb; } ul { list-style-type: none; padding: 0; } li { position: relative; background-color: #eee; padding: 12px 8px 12px 40px; font-size: 18px; cursor: pointer; transition: background-color 0.2s; user-select: none; margin-bottom: 5px; } li:nth-child(odd) { background-color: #f9f9f9; } li:hover { background-color: #ddd; } li.checked { background-color: #888; color: #fff; text-decoration: line-through; } li.checked::before { content: ' '; position: absolute; border-color: #fff; border-style: solid; border-width: 0 2px 2px 0; top: 10px; left: 16px; transform: rotate(45deg); height: 15px; width: 7px; } .close, .edit { position: absolute; top: 0; padding: 12px 16px; cursor: pointer; transition: background-color 0.3s, color 0.3s; } .close { right: 0; background-color: #f44336; color: white; } .edit { right: 36px; background-color: #33FF33; color: black; } .close:hover { background-color: #cc0000; } .edit:hover { background-color: #00cc00; } </style> </head> <body> <div id="boxThing"> <div id="myDIV" class="header"> <h2>To-Do List</h2> <input type="text" id="myInput" placeholder="Title..."> <span onclick="addNewElement()" class="addBtn">ADD</span> </div> <ul id="myUL"> <!-- 여기에 추가할 리스트 아이템들 --> </ul> </div> <script> // 리스트 아이템에 버튼 추가 및 이벤트 처리 function insertButtons(element) { var editSpan = document.createElement("SPAN"); editSpan.textContent = "edit"; editSpan.className = "edit"; element.appendChild(editSpan); var closeSpan = document.createElement("SPAN"); closeSpan.textContent = "\u00D7"; closeSpan.className = "close"; element.appendChild(closeSpan); } function addCloseFunctionality(closeItem) { closeItem.addEventListener("click", function() { var div = this.parentElement; div.style.display = "none"; }); } function addEditFunctionality(editItem) { editItem.addEventListener("click", function() { var div = this.parentElement; var newTxt = prompt("Todo 리스트 항목을 어떻게 수정할까요?"); if (newTxt === null || newTxt.trim() === "") { alert("다시 입력하십시오."); } else { div.textContent = newTxt; insertButtons(div); addLiveButtonFunctionality(); } }); } function addLiveButtonFunctionality() { var close = document.querySelectorAll(".close"); var edit = document.querySelectorAll(".edit"); close.forEach(addCloseFunctionality); edit.forEach(addEditFunctionality); } function addNewElement() { var inputValue = document.getElementById("myInput").value.trim(); if (inputValue === '') { alert("할 일을 입력하세요!"); } else { var li = document.createElement("li"); li.textContent = inputValue; document.getElementById("myUL").appendChild(li); insertButtons(li); addLiveButtonFunctionality(); } document.getElementById("myInput").value = ""; } document.addEventListener("keyup", function(event) { if (event.keyCode === 13) { addNewElement(); } }); var myNodelist = document.getElementsByTagName("LI"); Array.from(myNodelist).forEach(insertButtons); addLiveButtonFunctionality(); var list = document.querySelector('ul'); list.addEventListener('click', function(ev) { if (ev.target.tagName === 'LI') { ev.target.classList.toggle('checked'); } }); </script> </body> </html> ``` 이 코드에서는 다음과 같은 변경사항이 있습니다: 1. CSS와 HTML을 최대한 활용하여 스타일링을 했습니다. JavaScript는 주로 동적인 기능을 처리하도록 집중하였습니다. 2. 모든 스타일을 `<style>` 태그 내에 정의하는 대신, JavaScript에서 직접 스타일을 설정하는 방식으로 변경하였습니다. 3. `onclick` 이벤트를 HTML에서 JavaScript의 `addEventListener`로 대체하여 구조를 더 깔끔하게 만들었습니다. 4. 함수 이름을 좀 더 명확하게 지었습니다. 5. `Array.from()` 및 `forEach()`를 사용하여 반복 작업을 간결하게 처리했습니다. 6. 필요 없는 주석은 삭제하거나 보강하여 코드의 가독성을 높였습니다. 이러한 방식으로 코드를 리팩토링하면 유지보수가 쉬워지고, 코드의 가독성과 확장성이 향상됩니다.
- 카테고리
- #기타
댓글 0
추천 포스트