Steady-Dev
2022.11.08.
Javascript 네이밍(naming)과 클린 코드(clean code)

최적화

네이밍(naming)

언제나 힘들고 고민스러운 네이밍..😕 그 조건들을 정리합니다.

기본사항

변수명은 자체 설명적이어야 함

// bad
const value = 'Robin';
const val = 'Robin';

// good
const firstName = 'Robin';

자바스크립트에서는 camelCase, PascalCase를 사용


Arrays

변수 이름에 복수, 단수 표현하기

// bad
const fruit = ['apple', 'banana', 'cucumber'];
// okay
const fruitArr = ['apple', 'banana', 'cucumber'];
// good
const fruits = ['apple', 'banana', 'cucumber'];

// great
const fruitNames = ['apple', 'banana', 'cucumber'];
const fruits = [
  { name: 'apple', genus: 'malus' },
  { name: 'banana', genus: 'musa' },
  { name: 'cucumber', genus: 'cucumis' },
];

Booleans

is, has, can로 시작하여 변수의 타입을 암시하기

// bad
const open = true;
const write = true;
const fruit = true;
const equal = true;
const visible = true;

// good
const isOpen = true;
const canWrite = true;
const hasFruit = true;
const areEqual = true;
const isVisible = true;

만약 함수의 return 값이 boolean이라면?

함수명을 check나 get으로 시작하기

const user = { fruits: ['apple'] };
const checkHasFruit = (user, fruitName) => user.fruits.includes(fruitName);
const hasFruit = checkHasFruit(user, 'apple');

checkTodoData();

Numbers

변수명에 maximum, minimum, total 포함하기

// bad
const pugs = 3;

// good
const minPugs = 1;
const maxPugs = 5;
const totalPugs = 3;

Functions

동사를 사용하여 명명하기

// bad
userData(userId);
userDataFunc(userId);
totalOfItems(items);
elementValidator(elements);

// good
getUser(userId);
calculateTotal(items);
validateElement(elements);

to를 앞에 명명하는 것은 오래된 컨벤션

toDollars('euros', 20);
toUppercase('a string');

반복문 안에서의 변수는 단수형을 사용하기

// bad
const newFruits = fruits.map(x => {
  return doSomething(x);
});

// good
const newFruits = fruits.map(fruit => {
  return doSomething(fruit);
});

Components & Class

컴포넌트는 클래스와 같이 대문자로 사용

// bad
function userProfile(user) {
  return (
    <div>
      <span>First Name: {user.firstName}</span>
      <span>Last Name: {user.lastName}</span>
    </div>
  );
}

// good
function UserProfile(user) {
  return (
    <div>
      <span>First Name: {user.firstName}</span>
      <span>Last Name: {user.lastName}</span>
    </div>
  );
}

Constant

대문자에 _를 사용

var SECONDS = 60;
var MINUTES = 60;
var HOURS = 24;
var DAY = SECONDS * MINUTES * HOURS;
var DAYS_UNTIL_TOMORROW = 1;

Clean Code

많은 클린코드 규칙이 있지만 제가 놓치고 있던 것들 위주로 정리했습니다.

Object Lookup table

다수의 switch-case문은 JSON table화 하기

BAD

function getUserType(type) {
  switch (key) {
    case 'ADMIN':
      return '관리자';
    case 'INSTRUCTOR':
      return '강사';
    case 'STUDENT':
      return '학생';
    default:
      return '해당 없음';
  }
}

GOOD

function getUserType(type) {
  const USER_TYPE = {
    ADMIN: '관리자',
    INSTRUCTOR: '강사',
    STUDENT: '수강생',
  };

  return USER_TYPE[type] || '해당 없음';
}

긍정 우선 코드

BAD

if(!(typeof data === 'string')){ ... }

조건문엔 함수

어떤 연산을 위한 조건문인지 알아보기 쉬움

BAD

if(n % 1 === 0) { ... }

GOOD

if(isInt(n)) { ... }

중첩 그냥 두지말기

BAD

for (let i = range - 1; i < d.length; i++) {
  let sum = 0;
  for (let j = i - range + 1; j <= i; j++) {
    sum += d[j];
  }
  result.push(sum);
}

GOOD

for(let i=0; i<range; i++) {
	sum += d[i];
}

for(let k 1-i; j < d.length; j++) {
	if(j === (range-1)) {
		console.log(sum);
		continue;
	}
	let start = j - range;
	sum += d[j] - d[start];
	console.log(sum);
}

복잡한 것 숨기기

재사용 되는 건 덤

BAD

const validEmail =
  /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9+\.)+[a-zA-z]{2,}))$/.test(
    str,
  );

GOOD

const validEmail = isValidEmail(eMail);

short circuiting 대신 default parameters 사용

주의할 것은 undefined만 default parameter가 적용됩니다. 즉, falsy 값인 ‘’, “”, false, null, 0, NaN은 default value로 대체되지 않습니다.

BAD

function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.';
  // ...
}

GOOD

function createMicrobrewery(name = 'Hipster Brew Co.') {
  // ...
}

function parameters는 2개로 제한하기

BAD

function createMenu(title, body, buttonText, cancellable) {
  // ...
}

createMenu('Foo', 'Bar', 'Baz', true);

GOOD

function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true,
});

함수는 한가지 일만 수행하기

BAD

function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

GOOD

function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

함수는 하나의 개념으로 추상화하기

BAD

function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(' ');
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      // ...
    });
  });

  const ast = [];
  tokens.forEach(token => {
    // lex...
  });

  ast.forEach(node => {
    // parse...
  });
}

GOOD

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}

function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(' ');
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      tokens.push(/* ... */);
    });
  });

  return tokens;
}

function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token => {
    syntaxTree.push(/* ... */);
  });

  return syntaxTree;
}

Object.assign으로 object deafult value 설정

BAD

const menuConfig = {
  title: null,
  body: 'Bar',
  buttonText: null,
  cancellable: true,
};

function createMenu(config) {
  config.title = config.title || 'Foo';
  config.body = config.body || 'Bar';
  config.buttonText = config.buttonText || 'Baz';
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);

GOOD

const menuConfig = {
  title: 'Order',
  // User did not include 'body' key
  buttonText: 'Send',
  cancellable: true,
};

function createMenu(config) {
  let finalConfig = Object.assign(
    {
      title: 'Foo',
      body: 'Bar',
      buttonText: 'Baz',
      cancellable: true,
    },
    config,
  );
  return finalConfig;
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

flag를 함수의 매개변수로 사용하지 말기

BAD

function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

GOOD

function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

조건문 캡슐화

BAD

if (fsm.state === 'fetching' && isEmpty(listNode)) {
  // ...
}

GOOD

function shouldShowSpinner(fsm, listNode) {
  return fsm.state === 'fetching' && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

에러 무시하지 말기

BAD

try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

GOOD

try {
  functionThatMightThrow();
} catch (error) {
  // One option (more noisy than console.log):
  console.error(error);
  // Another option:
  notifyUserOfError(error);
  // Another option:
  reportErrorToService(error);
  // OR do all three!
}

에러를 catch 하기

BAD

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    console.log(error);
  });

GOOD

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
  });

함수 호출자와 피호출자는 가까이 두기

BAD

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

GOOD

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

리팩토링

  • 복잡한 코드가 나오기 전에 짧은 주기의 리펙토링 (설계 → 개발 → 리펙토링)
  • 리펙토링의 시작은 중복을 없애는 것
  • (클래스, 함수의) 크기만 줄여나가도 절반은 성공
  • 전역변수 제거

reference

https://hackernoon.com/the-art-of-naming-variables-52f44de00aad

https://www.robinwieruch.de/javascript-naming-conventions/

https://github.com/ryanmcdermott/clean-code-javascript

https://speakerdeck.com/nigayo/uahan-jabaseukeuribteu-gaebal

© 2023 datoybi.com
Powered By Gatsby. Hosted By Netlify.