どんなコードを書いたらコード転職できるのか?

7 min read読了の目安(約6600字

コード転職に関しては

  • 良い成績をとったけどバラマキっぽいオファーしかもらえなかった

  • どの回答も一定の範囲内のコードに収斂するので差別化 ( スキルをアピール ) できない

といった、やや否定的?な意見がある。

この意見に関しては同意できる部分もあるが、私自身は

  • コード転職サービスでバラマキではないオファーをいくつかもらった

    • 回答に使ったアルゴリズムについて言及されたスカウトを頂いたことがあり、ちゃんとコードをみてくれる企業が ( 一部かもしれないが ) 存在することは事実である
  • 同じアルゴリズムを使った回答でも、回答者によってコードの 綺麗 / 汚い ははっきり分かれる

という経験をしているので、コード転職において「納得いくオファーがもらえない」とか「スキルをアピールできない」理由は 提出したコード ( 回答者のスキル ) に問題がある とも思ってしまう。

では、どのようなコードを書けばスキルをアピールして納得いくオファーがもらえるのか?本記事では Paiza で実際に出題された問題を例に、評価 される / されない コードを解説する。

想定読者

  • コード転職に興味ある人

  • コード転職をする際に注意すべきことを知りたい人

例題

本記事では下記を例題として扱う。

この問題を例題として選んだ理由は

  • プロコン固有のアルゴリズム ( 動的計画法など ) を知らなくても回答できる

  • とはいえ、Paiza が主催する 年収保証スカウト の参加条件 ( B ランク以上 ) を満たす難易度である

  • コードの公開が許可されている

ことの 3 点である。これ以降で回答例を紹介するが、まずは上記の問題を各自解いてみて欲しい。大体 30-40 分程あれば回答できると思う。

Paiza の問題と回答は基本的に公開が NG なため、他の問題の回答を公開する際は公開 OK かを確認してからにすること

回答例

以下の 3 つは全て B ランクを取得できる ( Paiza が用意したテストコードを全てパスする ) コード である。では、あなたが採用する側の場合、どの回答を書いたエンジニアにスカウトを送りたいだろうか?

回答例 1
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(System.in)) {
            final int n = scanner.nextInt();
            final int m = scanner.nextInt();

            boolean[] seats = new boolean[n];
            for (int i = 0; i < m; i++) {
                final int a = scanner.nextInt();
                final int b = scanner.nextInt();
                boolean canSit = true;
                for (int j = 0; j < a; j++) {
                    final int p = (b + j) % n;
                    if (seats[p]) {
                        canSit = false;
                        break;
                    }
                }
                if (canSit) {
                    for (int j = 0; j < a; j++) {
                        final int p = (b + j) % n;
                        seats[p] = true;
                    }
                }
            }
            int ans = 0;
            for (boolean seat : seats) {
                if (seat) {
                    ans += 1;
                }
            }
            System.out.println(ans);
        }
    }
}
回答例 2
import java.util.Scanner;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(System.in)) {
            final int n = scanner.nextInt();
            final int m = scanner.nextInt();
            final int[] a = new int[m];
            final int[] b = new int[m];
            for (int i = 0; i < m; i++) {
                a[i] = scanner.nextInt();
                b[i] = scanner.nextInt();
            }
            System.out.println(solve(n, m, a, b));
        }
    }

    public static int solve(int n, int m, int[] a, int[] b) {
        boolean[] seats = new boolean[n];
        for (int i = 0; i < m; i++) {
            final int numbers = a[i];
            final int from = b[i];
            final boolean canSit = IntStream.range(0, numbers).noneMatch(p -> seats[(from + p) % n]);
            if (canSit) {
                for (int j = 0; j < numbers; j++) {
                    seats[(from + j) % n] = true;
                }
            }
        }
        return (int) IntStream.range(0, seats.length)
                .mapToObj(i -> seats[i])
                .filter(seat -> seat)
                .count();
    }
}
回答例 3
import java.math.BigInteger;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(System.in)) {
            final int n = scanner.nextInt();
            final int m = scanner.nextInt();
            final List<Group> groups = Stream.generate(() ->
                    new Group(scanner.nextInt(), scanner.nextInt())
            ).limit(m).collect(Collectors.toList());
            System.out.println(solve(n, groups));
        }
    }

    public static int solve(int n, List<Group> groups) {
        Seats seats = new Seats();
        for (Group group : groups) {
            Seats copy = seats.clone();
            for (int i = 0; i < group.numbers; i++) {
                final int p = (group.from + i) % n;
                if (seats.isOccupied(p)) {
                    seats = copy;
                    break;
                } else {
                    seats.occupy(p);
                }
            }
        }
        return seats.count();
    }

    private static class Group {
        public final int numbers;
        public final int from;

        public Group(int numbers, int from) {
            this.numbers = numbers;
            this.from = from;
        }
    }

    private static class Seats implements Cloneable {
        private BigInteger value = BigInteger.ZERO;

        public void occupy(int pos) {
            value = value.or(BigInteger.ONE.shiftLeft(pos));
        }

        public boolean isOccupied(int pos) {
            return value.and(BigInteger.ONE.shiftLeft(pos)).testBit(pos);
        }

        public int count() {
            return value.bitCount();
        }

        @Override
        public Seats clone() {
            try {
                return (Seats) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new AssertionError();
            }
        }
    }
}

総評

  • 回答例 1 は 関数の設計スキルJava の言語スキル が低い印象をレビュアーに与える

    • Main#main に全ての処理を実装しているため、関数の責務分割 ( 標準入力からの読み取り処理と、解を導出する処理の分割 ) ができておらず、単体テストが書きにくい
    • Java8 で導入された Stream API を使えそうな場面があるけど使っていない
  • 結果として回答者は 関数設計のスキルが低い人 とか 単体テストの経験が浅い人 とか Java のスキルが低い、または最近の Java の言語機能を知らない ( 勉強熱心ではない ) 人 といった印象を持たれる

  • これでは希望するオファーをもらえなくて当然である

    • 読者の中には「意図的に問題あるコードを書いたのでは?」と思う人もいるかもしれないが、本問題の回答例を検索すると似たようなコードがいくつか見つかる
  • 一方、回答例 2 は回答例 1 の指摘を修正したバージョンであり、回答例 1 のようなネガティブな印象を持たれるリスクは減る

  • ただし データ構造の設計に関するスキル がやや低い印象をレビュアーに与える

    • Main#solve の引数に渡す ab はペアで使用するため、独自型を用意してひとまとめにしたい
  • 回答例 3 はオブジェクト指向を取り入れていて、先に示した 2 つの例とは全く異なる実装になっている

    • 実装の詳細が隠蔽されているため、メインロジックである Main#solve の可読性が他の回答例よりも高い

まとめ

回答例 1-3 で示した通り、プロコンのような設計・実装スキルを差別化するのが難しそうな問題でも、回答者のスキル次第でスキルをアピールすることは可能である。もしコード転職で思うような結果を得られてない場合は

  • 自身の 設計 / 実装 スキルを磨き直す

  • 良い成績を取ることより綺麗なコードを書くことを意識する

ことに取り組んでほしい。良い成績を取らないと申し込めない企業もあるため成績にこだわる気持ちもわかるが、良い成績を取れてもイマイチな回答では希望するオファーは来ない

本記事が、コード転職をしたいがなかなか良いオファーをもらえないとか、何に気をつけるべきか分からないとかいった方の参考になれば幸いである。