【駆け出しVSシニア】あなたは綺麗にかけるか？厳選10問

# はじめに

**これから出題する問題に答えるとあなたが駆け出しorシニアエンジニアかわかります**

こんにちは、Watanabe Jin(@Sicut_study)です。
今回はJavaScriptを用いた駆け出しエンジニアとシニアエンジニアで書き方がはっきり分かれる問題を厳選して10問用意しました。

問題を解くことでおおよそあなたがどのレベルなのかを予想することが可能となっています。
今回は以下の3つのレベルごとに模範解答を用意しています。

- 駆け出しエンジニアレベル
- ジュニアエンジニアレベル
- ミドル〜シニアレベル

この問題を解くことで綺麗にコーディングするためのノウハウを学べることはもちろんのこと、普段利用していない便利な文法を知るきっかけになるかもしれません

まずは答えを見ずに自分で解いてみることをおすすめします！

# 動画での解説

こちらの記事は動画でも解説していますので、丁寧に学びたい方はぜひ活用ください

https://youtu.be/tRPjREF9Tzk?si=POsyDPlg4C-25tmn

# 問題の解き方

全部で問題は10問用意しています。難易度順になっていますので1問目から解いてみてください。

1. 問題をみて自分で解く
2. 駆け出し -> ジュニア -> シニアの順で模範解答を用意しているのでどのコードに近いかを見比べる
3. 足りなかった考え方を今後に活かす

# 1. コンソール


:::note info
ユーザーのオブジェクトをコンソールで表示してください
:::

```js
const user1 = { name: "watanabe", age: 27, isMen: true }
const user2 = { name: "suzuki", age: 25, isMen: false }
```

### 駆け出しエンジニア

```js
const user1 = { name: "watanabe", age: 27, isMen: true }
const user2 = { name: "suzuki", age: 25, isMen: false }

console.log(user1)
console.log(user2)
```

### ジュニアエンジニア

```js
const user1 = { name: "watanabe", age: 27, isMen: true }
const user2 = { name: "suzuki", age: 25, isMen: false }

console.log({ user1, user2 })
```

一度で書きたい場合ですが、駆け出しエンジニアの方法でもいいと思います

### シニアエンジニア

```js
const user1 = { name: "watanabe", age: 27, isMen: true }
const user2 = { name: "suzuki", age: 25, isMen: false }

console.table([user1, user2])
```

```
┌─────────┬────────────┬─────┬───────┐
│ (index) │    name    │ age │ isMen │
├─────────┼────────────┼─────┼───────┤
│    0    │ 'watanabe' │ 27  │ true  │
│    1    │  'suzuki'  │ 25  │ false │
└─────────┴────────────┴─────┴───────┘
```

console.tableを使うと見やすくなるのでおすすめです

# 2. 分割代入

:::note info
オブジェクトのプロパティを関数の引数として分割代入を使用してください
:::

```js
const user = { name: "watanabe", age: 27, isMen: true }

// watanabe is 27 years oldと表示する
```

### 駆け出しエンジニア

```js
function feed(user) {
  console.log(`${user.name} is ${user.age} years old`)
}
feed(user)
```

### ジュニアエンジニア

```js
function feed(user) {
  const { name, age } = user
  console.log(`${name} is ${age} years old`)
}
feed(user)
```

### シニアエンジニア

```js
function feed({ name, age }) {
  console.log(`${name} is ${age} years old`)
}
feed(user)
```

分割代入を使用すると、関数の引数としてオブジェクトのプロパティを直接受け取ることができ、コードが簡潔になりますね

# 3. テンプレートリテラル

:::note info
オブジェクトのプロパティを使って年齢に基づいて異なる文字列を生成してください。
:::

```js
const user = { name: "watanabe", age: 27, isMen: true }

// 18歳以上ならI'm an adult at 27 years old
// 18歳未満ならI'm an young at 15 years old
```

### 駆け出しエンジニア

```js
const ageStr = user.age > 18 ? "adult" : "young";
const bio = "I'm an " + ageStr + " at " + user.age + " years old";
console.log(bio);
```

### ジュニアエンジニア

```js
const { age } = user
const ageStr = age > 18 ? "adult" : "young";
const bio = `I'm an ${ageStr} at ${age} years old`;
console.log(bio);
```

テンプレートリテラルを利用して実装しました

### シニアエンジニア

```js
function userAge(str, age) {
  const ageStr = age > 18 ? "adult" : "young";
  return `${str[0]}${ageStr} at ${age} years old`;
}

const bio2 = userAge`I'm a ${user.age}`;
console.log(bio2);
```

タグ付きテンプレートを利用してみました。ただこれを普段から利用するかというと微妙なところもあります。逆に分かりづらくなることもあるので注意が必要です。

# 4. 配列操作

:::note info
配列の各要素を操作し、合計値、税金を含む新しい配列、および100いじょうを満たす要素の配列を作成してください。
:::

```js
const orders = [500, 30, 99, 15, 223];
```

### 駆け出しエンジニア

```js
let total = 0;
const withTax = [];
const highValue = [];

for (let i = 0; i < orders.length; i++) {
  total += orders[i];
  withTax.push(orders[i] * 1.1);
  if (orders[i] >= 100) {
    highValue.push(orders[i]);
  }
}

console.log(total);
console.log(withTax);
console.log(highValue);
```

### ジュニアエンジニア

```js
let total = 0;
const withTax = [];
const highValue = [];

orders.forEach(order => {
  total += order;
  withTax.push(order * 1.1);
  if (order >= 100) {
    highValue.push(order);
  }
});

console.log(total);
console.log(withTax);
console.log(highValue);
```

forEachを用いて合計値を計算してみました

### シニアエンジニア

```js
const total = orders.reduce((acc, cur) => acc + cur, 0);
const withTax = orders.map(v => v * 1.1);
const highValue = orders.filter(v => v >= 100);

console.log(total);
console.log(withTax);
console.log(highValue);
```

配列メソッドを利用することでシンプルに書けるようになりました

# 5. ネストの多い条件分

:::note info
以下の要件を満たす実装をしてくださ
:::

機能概要
authCheck関数は、ユーザーの接続状態、ログイン状態、および管理者権限をチェックし、適切なメッセージを表示する機能を持っています。条件を満たしていれば管理パネルへのアクセスを許可します。

引数
この関数は引数を取りません。

前提条件
wifi: ブール値で、Wi-Fi接続があるかどうかを示します。
login: ブール値で、ユーザーがログインしているかどうかを示します。
admin: ブール値で、ユーザーが管理者であるかどうかを示します。
seeAdminPanel: 管理パネルにアクセスするための関数。

挙動
Wi-Fiに接続されているかをチェックします。
接続されていない場合、"Please connect to wifi" とコンソールに表示して関数を終了します。
ユーザーがログインしているかをチェックします。
ログインしていない場合、"Please login first" とコンソールに表示して関数を終了します。
ユーザーが管理者であるかをチェックします。
管理者でない場合、"You are not an admin" とコンソールに表示して関数を終了します。
上記の条件を全て満たしている場合、seeAdminPanel 関数を呼び出します。

戻り値
この関数は値を返しません。

### 駆け出しエンジニア

```js
function authCheck() {
    if (wifi) {
        if(login) {
            if (admin) {
                seeAdminPanel();
            } else {
                console.log('You are not an admin');
            }
        } else {
            console.log('Please login first');
        }
    } else {
        console.log('Please connect to wifi');
    }
}
```

### ジュニアエンジニア

```js
function authCheck() {
    if (!wifi) {
        console.log('Please connect to wifi');
        return;
    }

    if (!login) {
        console.log('Please login first');
        return;
    }

    if (!admin) {
        console.log('You are not an admin');
        return;
    }

    seeAdminPanel();
}

authCheck();
```

ネストを減らして条件チェックを行いました

### シニアエンジニア

```js
function authCheck() {
    if (!wifi) {
        return console.log('Please connect to wifi');
    }

    if (!login) {
        return console.log('Please login first');
    }

    if (!admin) {
        return console.log('You are not an admin');
    }

    seeAdminPanel();
}

authCheck();
```

早期リターンを使うことでシンプルにしました

簡単だったでしょうか？ここは駆け出しエンジニアとそうでないかで分かれる問題かなと思います

# 6. Switchを用いた条件分

:::note info
以下の要件を満たす実装をしてください
:::

機能概要
getStatus関数は、ステータスコードに基づいて特定の処理を実行する関数です。指定されたステータスコードに応じた処理をスイッチ文で分岐します。

引数
statusCode: 数値型の引数。処理を決定するためのステータスコードです。

定数
定数値 1 を持つ。アクティブ状態を表します。
定数値 2 を持つ。非アクティブ状態を表します。
定数値 3 を持つ。保留状態を表します。

挙動
statusCodeの値をスイッチ文で評価します。
各ケースに対して特定の処理を実行します（具体的な処理はコメントで示されています）。
statusCodeが定義されたどのケースにも該当しない場合、デフォルトの処理を実行します。

戻り値
この関数は値を返しません。

### 駆け出しエンジニア

```js
function getStatus(statusCode) {
  switch (statusCode) {
    case 1:
      // do something
      break;
    case 2:
      // do something
      break;
    case 3:
      // do something
      break;
    default:
      // do something
      break;
  }
}
```

### ジュニア・シニアエンジニア

```js
const ACTIVE = 1;
const INACTIVE = 2;
const PENDING = 3;

function getStatus(statusCode) {
    switch (statusCode) {
        case ACTIVE:
        // do something
        break;
        case INACTIVE:
        // do something
        break;
        case PENDING:
        // do something
        break;
        default:
        // do something
        break;
    }
}
```

マジックナンバーを利用するとコードが理解しづらいのでステータスコードに名前をつけて分岐させることで処理を理解できるようになりました

このようなパターンではenumのようなものを使うことも多いですね

# 7. 価格計算関数のリファクタリング

:::note info
メンバーおよびゲストの価格を計算する関数をリファクタリングしてください。
:::

機能概要
getPrice関数は、指定された価格に基づいてメンバーまたはゲストの価格を計算します。メンバーには手数料が引かれた価格を、ゲストには課税された価格を返します。

### 駆け出しエンジニア

```js
// メンバー価格を計算する関数
function getMemberPrice(price) {
  const taxable = price * 1.1;
  const fee = 1000;
  return taxable - fee;
}

// ゲスト価格を計算する関数
function getGuestPrice(price) {
  const taxable = price * 1.1;
  return taxable;
}

const memberPrice = getMemberPrice(1000);
const guestPrice = getGuestPrice(1000);

console.log(`Member Price: ${memberPrice}`); // "Member Price: 80"
console.log(`Guest Price: ${guestPrice}`); // "Guest Price: 1080"
```

### ジュニアエンジニア

```js
function getPrice(price, isMember) {
  const taxable = price * 1.1;
  if (isMember) {
    const fee = 1000;
    return taxable - fee;
  } else {
    return taxable;
  }
}

const memberPrice = getPrice(1000, true);
const guestPrice = getPrice(1000, false);

console.log(`Member Price: ${memberPrice}`); // "Member Price: 80"
console.log(`Guest Price: ${guestPrice}`); // "Guest Price: 1080"
```

引数でメンバーかを判定できるようにしました

### シニアエンジニア

```js
function getPrice(price, role) {
  const taxable = price * 1.08;
  switch (role) {
    case "member":
      const fee = 1000;
      return taxable - fee;
    case "guest":
      return taxable;
    default:
      throw new Error("Invalid role");
  }
}

const memberPrice = getPrice(1000, "member");
const guestPrice = getPrice(1000, "guest");

console.log(`Member Price: ${memberPrice}`); // "Member Price: 80"
console.log(`Guest Price: ${guestPrice}`); // "Guest Price: 1080"
```

メンバーとゲストをスイッチ分で分けました

共通化できるコードは積極的に1つに集めていくことが大切になります

# 8. 会計表記変換

:::note info
数値を会計表記に変換する関数を実装してください。
負の数値を括弧付きの表記に変換し、正の数値をそのまま返す関数を実装してください。
:::

### 駆け出しエンジニア

```js
function toAccounting(n) {
  if (n < 0) {
    return "(" + Math.abs(n) + ")";
  } else {
    return n.toString();
  }
}

console.log(toAccounting(0));    // "0"
console.log(toAccounting(10));   // "10"
console.log(toAccounting(-5));   // "(5)"
```

### ジュニアエンジニア

```js
function numberToAccountingString(number) {
  if (number != null) {
    if (number < 0) {
      return `(${Math.abs(number)})`;
    } else {
      return number.toString();
    }
  }
}

console.log(numberToAccountingString(undefined)); // undefined
console.log(numberToAccountingString(0));         // "0"
console.log(numberToAccountingString(10));        // "10"
console.log(numberToAccountingString(-5));        // "(5)"
```

nullチェックをして例外に強くしました

### シニアエンジニア

```js
function numberToAccountingString(number) {
  if (number == null) return;
  if (number < 0) return `(${Math.abs(number)})`;
  return number.toString();
}

console.log(numberToAccountingString(undefined)); // undefined
console.log(numberToAccountingString(0));         // "0"
console.log(numberToAccountingString(10));        // "10"
console.log(numberToAccountingString(-5));        // "(5)"
```
早期リターンを使いました
シニアエンジニアほど例外をしっかりと考慮をしつつシンプルに実装をできますね

# 9. 合計金額計算関数のリファクタリング

:::note info
商品のリストとオプションに基づいて合計金額を計算する関数を実装してください。
:::

機能概要
calculateTotal関数は、商品のリストとオプションの割引や送料に基づいて、総金額を計算します。

引数
items: オブジェクトの配列。各オブジェクトは以下のプロパティを持つ:
price: 数値型。商品の価格。
quantity: 数値型。商品の数量。
options (オプション): オブジェクト。以下のプロパティを持つ:
discount (省略可能): 数値型。割引率 (0 から 1 までの値)。
shipping (省略可能): 数値型。送料。
戻り値
商品の総金額を数値型で返します。

挙動
各商品の価格と数量の積を合計します。
合計金額に対して、割引率が指定されている場合は割引を適用します。
合計金額に対して、税率を適用します。
合計金額に対して、送料が指定されている場合はそれを加算します。送料が0の場合は送料を加算しません。送料が指定されていない場合は、デフォルト値の送料を加算します。

定数
TAX_RATE: 定数値 1.1。税率。
SHIPPING_DEFAULT: 定数値 500。デフォルトの送料。

### 駆け出しエンジニア

```js
function calculateTotal(items, options) {
    let t = 0;
    items.forEach(i => {
        t += i.price * i.num;
    });
    t = t - t * (options.dis || 1);
    t = t * 1.1;
    t = t + (option.ship || 500)
    return t;
}

const testItems = [
    { price: 1500, num: 2 },
    { price: 2000, num: 1 },
    { price: 500, num: 4 }
];

console.log(calculateTotal(testItems, {}));           // 合計金額を計算
console.log(calculateTotal(testItems, { ship: 0 }));  // 送料なし
console.log(calculateTotal(testItems, { dis: .75 })); // 割引あり
console.log(calculateTotal(testItems, { ship: 1200 })); // 送料あり
```

### ジュニアエンジニア
```js
const TAX_RATE = 1.1;
const DEFAULT_SHIPPING = 500;

function calculateTotal(items, options = {}) {
    if (items == null || items.length === 0) return 0;

    let total = 0;

    items.forEach(item => {
        total += item.price * item.num * (options.discount ?? 1);
    });

    total *= TAX_RATE;

    if (options.shipping !== 0 || !options.shipping) {
        total += (options.shipping || DEFAULT_SHIPPING);
    }

    return total;
}

const testItems = [
    { price: 1500, num: 2 },
    { price: 2000, num: 1 },
    { price: 500, num: 4 }
];

console.log(calculateTotal(testItems, {}));           // 合計金額を計算
console.log(calculateTotal(testItems, { shipping: 0 }));  // 送料なし
console.log(calculateTotal(testItems, { discount: 0.75 })); // 割引あり
console.log(calculateTotal(testItems, { shipping: 1200 })); // 送料あり

```

商品のリストとオプションを受け取るようにしました。また定数を導入して明確な変数名にしました
送料はデフォルトで500円として定義しておきました

### シニアエンジニア

```js
const TAX_RATE = 1.1
const DEFAULT_SHIPPING = 500

function calculateTotal(items, options = {}) {
    const { shipping = DEFAULT_SHIPPING, discount = 1 } = options;
    if (items == null || items.length === 0) return 0

    let total = items.reduce((acc, item) => {
        return acc + item.price * item.num * discount;
    }, 0);


    total *= TAX_RATE

    if (shipping !== 0) {
        total += shipping
    }

    return total
}

const testItems = [
    { price: 1500, num: 2 },
    { price: 2000, num: 1 },
    { price: 500, num: 4 }
];

console.log(calculateTotal(testItems, {}));           // 合計金額を計算
console.log(calculateTotal(testItems, { ship: 0 }));  // 送料なし
console.log(calculateTotal(testItems, { discount: .75 })); // 割引あり
console.log(calculateTotal(testItems, { ship: 1200 })); // 送料あり
```

合計を検索する関数をreduceでリファクタリングしました
またオプションのデフォルト値を分割代入にしました

# 10. スロット

これは古いJavaScriptの書き方になるので駆け出しでやっている方がいることは少ないかもしれないですが、最後におまけの問題として作成しました。

:::note info
スロットゲームを行います。以下の関数を3回実行して3つの数字を表示してください
:::

```js
function randomSlotNumber() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // 1から9の数字を返す
            resolve(Math.floor(Math.random() * 9) + 1);
        }, 1000);
    });
}
```

### 駆け出しエンジニア?

```js
const startSlotMachine = () => {
  let first;
  let second;
  let third;

  return randomSlotNumber().then((result) => {
    first = result;
    return randomSlotNumber();
  }).then((result) => {
    second = result;
    return randomSlotNumber();
  }).then((result) => {
    third = result;
    console.log(first, second, third);
  })
}

startSlotMachine()
```

最近は聞かなくなったコールバック地獄になっているコードです。

### シニアエンジニア

```js
const startSlotMachine = async () => {
    const first = await randomSlotNumber();
    const second = await randomSlotNumber();
    const third = await randomSlotNumber();
    console.log(first, second, third);
}

startSlotMachine();
```

awaitを使うことですっきりかけるようになりました

# おわりに

今回はJavaScriptの悪いコードの書き方をきれいにしていく問題を10問ご紹介しました。
あなたはどれくらいシニアエンジニアレベルで書けたでしょうか？

コーディングをきれいに行うのは、コードをきれいにかける人からレビューしてもらったり、素晴らしいコードを読むのが良いですがなかなか駆け出しエンジニアの方には機械がないと思ったのでこのような記事を書いてみました。

JavaScript以外にも問題は作れるので評判がよければまたやります！

ここまで読んでいただけた方はいいねとストックよろしくお願いします。
@Sicut_study をフォローいただけるととてもうれしく思います。

普段はTwitterでエンジニアに関する情報を発信していますのでよければ友達になってください👇

https://x.com/Sicut_study

また明日の記事でお会いしましょう！

# JISOUのメンバー募集中！
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか？

興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください！
▼▼▼

https://projisou.jp