못난 코드 개선하기

yun·2024년 2월 12일
0

C++

목록 보기
35/41
post-thumbnail

전에 이런 글을 봤었는데,

요즘 나는 '일단 돌아가게 만들자'하고 막 짠 코드를 못 고치는 일이 다반사다. 다른 일을 하다 보면 그 코드는 유지보수가 어려운 스파게티가 되어간다.

개발을 하랬더니 스파게티 요리사가 되면 어떡해...

얼마 전에 코테에 제출한 코드의 상태가 심각해서 지금이라도 고쳐본다.

코테 사이트는 coderbyte였고, 머리에 남은 문제 내용으로 복기해 본다.

문제

입력은 크기가 11인 배열로 주어지고, 이 배열은 빈칸이 있는 틱택토 게임의 보드 형태이다. 배열의 각 원소는 줄바꿈을 표시하는 "<>", 또는 빈칸을 표시하는 "-", 또는 사용자가 놓은 돌을 표시하는 "X", "O"를 가질 수 있다. 어떤 칸에 돌을 놓으면 승리하는지, 그 칸의 번호를 반환하는 함수를 작성하시오.

제출한 코드

#include <iostream>
#include <string>
using namespace std;

string blank = "-";

bool isblank(string& c)
{
  return blank.compare(c) == 0;
}

bool is_same_str(string& a, string& b)
{
  return a.compare(b) == 0;
}

int GameChallenge(string strArr[], int arrLength) {
  int result = 0;

  for (int i = 0; i < 11; ++i)
  {
    if ( isblank(strArr[i]) )
    {

      // horizontal
      if ( !isblank(strArr[i+1]) && !isblank(strArr[i+2]) 
      && is_same_str(strArr[i+1], strArr[i+2]) ) return i;

      if ( !isblank(strArr[i-1]) && !isblank(strArr[i+1]) 
      && is_same_str(strArr[i-1], strArr[i+1]) ) return i;

      if ( !isblank(strArr[i-2]) && !isblank(strArr[i-1]) 
      && is_same_str(strArr[i-2], strArr[i-1]) ) return i;

      // vertical
      if ( i < 3 
      && !isblank(strArr[i+4]) && !isblank(strArr[i+8]) 
      && is_same_str(strArr[i+4], strArr[i+8]) ) return i;

      if ( i > 3 && i < 7
      && !isblank(strArr[i-4]) && !isblank(strArr[i+4]) 
      && is_same_str(strArr[i-4], strArr[i+4]) ) return i;

      if ( i > 7
      && !isblank(strArr[i-8]) && !isblank(strArr[i-4]) 
      && is_same_str(strArr[i-8], strArr[i-4]) ) return i;

      // diagonal
      if ( i == 5
      && !isblank(strArr[0]) && !isblank(strArr[10]) 
      && is_same_str(strArr[0], strArr[10]) ) return i;

      if ( i == 5
      && !isblank(strArr[2]) && !isblank(strArr[8]) 
      && is_same_str(strArr[2], strArr[8]) ) return i;

      if ( i == 0
      && !isblank(strArr[5]) && !isblank(strArr[10]) 
      && is_same_str(strArr[5], strArr[10]) ) return i;

      if ( i == 10
      && !isblank(strArr[0]) && !isblank(strArr[5]) 
      && is_same_str(strArr[0], strArr[5]) ) return i;

      if ( i == 2
      && !isblank(strArr[5]) && !isblank(strArr[8]) 
      && is_same_str(strArr[5], strArr[8]) ) return i;

      if ( i == 8
      && !isblank(strArr[2]) && !isblank(strArr[5]) 
      && is_same_str(strArr[2], strArr[5]) ) return i;
    }
  }

  return result;
}

int main(void) { 
   
  // keep this function call here
  string A[] = coderbyteInternalStdinFunction(stdin);
  int arrLength = sizeof(A) / sizeof(*A);
  cout << GameChallenge(A, arrLength);
  return 0;
    
}

어떤 칸이 빈칸이고 다른 조건(가로, 세로, 대각선)을 만족한다면, 그 인덱스를 반환하는 코드이다.

coderbyte~ 함수는 코테 사이트의 내장함수이다.

처음에는 정말 모든 케이스를 작성했고, 가로/세로는 그나마 줄여봤는데 대각선은 규칙으로 만들지 못해서 완전한 하드코딩.

로직 외에도 const를 써야 하는데 쓰지 않은 부분도 걸리고, 레퍼런스 타입도 bad_alloc 에러를 여러 번 만나고 나서야 썼다.

수정한 코드

#include <iostream>
#include <string>
#include <vector>
#include <sstream>  // input parsing
#include <algorithm>  // remove

using namespace std;

const string BLANK = "-";
const int BOARD_SIZE = 11;
const int ROW_SIZE = 3;

bool isBlank(const string& c)
{
  return BLANK.compare(c) == 0;
}


bool isSame(const string& a, const string& b)
{
  return a.compare(b) == 0;
}


int checkHorizontal(const int i, const vector<string>& strArr) {
  // - O O
  if ( i < BOARD_SIZE - 2
  && !isBlank(strArr[i+1]) && !isBlank(strArr[i+2]) 
  && isSame(strArr[i+1], strArr[i+2]) ) return i;

  // O - O
  if ( i < BOARD_SIZE - 1
  && !isBlank(strArr[i-1]) && !isBlank(strArr[i+1]) 
  && isSame(strArr[i-1], strArr[i+1]) ) return i;

  // O O -
  if ( !isBlank(strArr[i-2]) && !isBlank(strArr[i-1]) 
  && isSame(strArr[i-2], strArr[i-1]) ) return i;

  return -1;
}


int checkVertical(const int i, const vector<string>& strArr) {
  // -
  // O
  // O
  if ( i < 3 
  && !isBlank(strArr[i+4]) && !isBlank(strArr[i+8]) 
  && isSame(strArr[i+4], strArr[i+8]) ) return i;

  // O
  // -
  // O
  if ( i > 3 && i < 7
  && !isBlank(strArr[i-4]) && !isBlank(strArr[i+4]) 
  && isSame(strArr[i-4], strArr[i+4]) ) return i;

  // O
  // O
  // -
  if ( i > 7
  && !isBlank(strArr[i-8]) && !isBlank(strArr[i-4]) 
  && isSame(strArr[i-8], strArr[i-4]) ) return i;

  return -1;
}

int checkFirstDiagonal(const int i, const vector<string>& strArr) {
  // -
  //   O
  //     O
  if ( i == 0
  && !isBlank(strArr[5]) && !isBlank(strArr[10]) 
  && isSame(strArr[5], strArr[10]) ) return i;

  // O
  //   -
  //     O
  if ( i == 5
  && !isBlank(strArr[0]) && !isBlank(strArr[10]) 
  && isSame(strArr[0], strArr[10]) ) return i;

  // O
  //   O
  //     -
  if ( i == 10
  && !isBlank(strArr[0]) && !isBlank(strArr[5]) 
  && isSame(strArr[0], strArr[5]) ) return i;

  return -1;
}


int checkSecondDiagonal(const int i, const vector<string>& strArr) {
  //     -
  //   O
  // O
  if ( i == 2
  && !isBlank(strArr[5]) && !isBlank(strArr[8]) 
  && isSame(strArr[5], strArr[8]) ) return i;

  //      O
  //   -
  // O
  if ( i == 5
  && !isBlank(strArr[2]) && !isBlank(strArr[8]) 
  && isSame(strArr[2], strArr[8]) ) return i;

  //     O
  //   O
  // -
  if ( i == 8
  && !isBlank(strArr[2]) && !isBlank(strArr[5]) 
  && isSame(strArr[2], strArr[5]) ) return i;

  return -1;
}


int checkDiagonal(const int i, const vector<string>& strArr) {
  // 우측으로 내려오는 대각선을 주 대각선, 좌측으로 내려오는 대각선을 부 대각선이라고 하자
  // 사이즈 11인 배열에서
  // 주 대각선은 0, 5, 10 인덱스를 반환할 수 있다
  // 부 대각선은 2, 5, 8 인덱스를 반환할 수 있다
  int firstDiagonalIdx = checkFirstDiagonal(i, strArr);
  if (firstDiagonalIdx != -1) return firstDiagonalIdx;

  int secondDiagonalIdx = checkSecondDiagonal(i, strArr);
  if (secondDiagonalIdx != -1) return secondDiagonalIdx;

  return -1;
}


int GameChallenge(const vector<string>& strArr) {
  for (int i = 0; i < BOARD_SIZE; ++i)
  {
    if ( isBlank(strArr[i]) )
    {
      // cout << i << endl;

      int horizontalIdx = checkHorizontal(i, strArr);
      // cout << "horizontalIdx: " << horizontalIdx << endl;
      if (horizontalIdx != -1) return horizontalIdx;

      int verticalIdx = checkVertical(i, strArr);
      // cout << "verticalIdx: " << verticalIdx << endl;
      if (verticalIdx != -1) return verticalIdx;

      int diagonalIdx = checkDiagonal(i, strArr);
      // cout << "diagonalIdx: " << diagonalIdx << endl;
      if (diagonalIdx != -1) return diagonalIdx;
    }
  }

  return -1;
}

vector<string> parseInput(string input) {
  vector<string> result;
  string token;
  istringstream tokenStream(input);
  char delimiter = ',';

  while (getline(tokenStream, token, delimiter)) {
    // 공백, 중괄호, 큰따옴표 제거
    token.erase(remove(token.begin(), token.end(), ' '), token.end());
    token.erase(remove(token.begin(), token.end(), '{'), token.end());
    token.erase(remove(token.begin(), token.end(), '}'), token.end());
    token.erase(remove(token.begin(), token.end(), '"'), token.end());
    if (!token.empty()) {
        result.push_back(token);
    }
  }

  return result;
}

int main(void) { 
  string inputLine;
  getline(cin, inputLine);
  vector<string> A = parseInput(inputLine);
  
  cout << GameChallenge(A);

  return 0;
}

[테스트 케이스 1]

  • 입력: {"X", "-", "X", "<>", "O", "-", "X", "<>", "X", "O", "O"}
  • 출력: 1

[테스트 케이스 2]

  • 입력: {"X", "X", "O", "<>", "O", "X", "X", "<>", "X", "O", "-"}
  • 출력: 10

[고친 점]

  • 상수, 파라미터로 전달받고 변형하지 않는 값에 const 사용
  • (웹사이트 내장함수가 없으므로) 입력값 vector로 받아서 파싱
  • 가로 성공 점검 시 i 범위값 제한이 없어 에러 발생하며 프로그램 종료 버그 수정 -> BOARD_SIZE - 2 / BOARD_SIZE - 1 등 조건 추가
  • 주석 -> 기능별 함수 분리

[아직 못 고친 점]

  • 하드코딩에서 규칙발견해서 규칙 기반 코드로 고치기 (특히 대각선)

0개의 댓글