ベルリンのITスタートアップで働くジャバ・ザ・ハットリの日記

日本→シンガポール→ベルリンへと流れ着いたソフトウェアエンジニアのブログ

【読者質問 02】日本と海外のエンジニアの「平均的な技術力」に違いはあるか?

ご質問ありがとうございます。ではさっそく質問から

ブログを読ませていただいた限り、面接でプログラミングテストをされることが一般的なようですが、海外のエンジニアの方は、普段からアルゴリズムの知識、ネットワークの知識、コンピュータの知識について詳細に語れるくらいの知識を持っているものなのでしょうか。日本のエンジニアと海外のエンジニアの「平均的な技術力」にどのような違いがありますか。

知識の有無に関しては日本と英語圏のエンジニアの間にそれほど違いがあると思ったことはない。優秀な人達やそうでもない人達が混ざり合っているのが社会だからだ。しかし人はそれぞれの環境に合わせて最適化されるので「環境の違いがエンジニアひとりひとりの違いに色濃く出ているなー」というのが私なりの回答になる。

まず質問の前半部分にある「色んな知識を語れるぐらいか?」について。どれぐらい語れるかにもよるが、普通にITエンジニアをしている人でアルゴリズムが一体ナニなのかまったく知らない、なんて人は日本にも英語圏にもほとんど居ないだろう。ひとりひとりのエンジニアと向き合って話せばそれが日本人でもアメリカ人でも「よく知ってる奴も居れば知らない奴も居る」というのが実情。
元Googleエンジニアに「やっぱりGoogle社内ってのはめちゃくちゃに優秀な奴ばっかりか?」と聞いたことがあるが、その率直な答えは「いや、そんなことねーよ。平均すれば技術に長けた人が多い会社であることは間違い無いが、なんといっても大企業だし従業員数が多いんだ。中には『なんでこの人がグーグラーなの?』ってのも居るって」だった。感覚値なので真意を確かめる術は無いが、たくさん人が居ることは様々なレベルの人が居る、ということなのだろう。
したがってひとことで「海外のエンジニア」と言ってもレベルは様々です、が回答になる。

質問の後半部分の「技術力の違い」について。これは技術レベルの高低ではなく、種類の違いを感じる。なぜならエンジニアが置かれている環境が違うからだ。英語圏のエンジニアの労働環境が日本のと異なる点は大きく言うと以下の3点。

  1. できなければクビが普通にある
  2. ジョブスコープが決まっている
  3. SEがいない

それぞれがそのまま英語圏のエンジニアの特徴にも言い換えられる。

1) できなければクビが普通にある
できない人はクビになってしまうので、極端に技術力が無いのになぜがずっと在籍している人というのを見たことがない。こうして文字にすると「なんで技術力が無くてエンジニアチームに在籍してる奴が居るんだよ?」と当然な質問が出てきそうだが、少なくとも私が日本に居た時は確かにそういう方が居た。「ちょっとこの人ってエンジニアに向いてないな」という人が居て、それでも不思議なぐらいにクビにならずに済んでいた。というか日本の会社である朝出社したら同僚がクビになって消えていた、なんてことは聞いたこと無かった。ある意味で日本的雇用のいいところかもしれないが英語圏では普通にクビがある。なんでもお構いなしにばんばんクビを切ってくるなんて無慈悲な世界ではないが、あることはある。したがって英語圏では極端にデキない人というのは無い。

2)ジョブスコープが決まっている
入社する前からジョブスコープという「あなたにはこれをやってもらいます」が決まっているので、まったく畑違いのことを頼まれることは少ない。そのためいわゆる英語圏のエンジニアは専門バカ的な感じがある。ウェブ系の場合、フロントエンドとバックエンドのエンジニアは違う人になっている。iOSとAndroidをひとりのエンジニアが両方見ることはあったが、ひとりでフロントからモバイルアプリ、ウェブ、バックエンド、インフラまで全部やります、って人には会ったことが無い。でも日本のブログなんかにあるエンジニアの経歴にはそういう全方向OKです!みたいな方が居てちょっと感心してしまう。
英語圏でそういうマルチな能力をまったく必要としていない訳ではないと思う。しかし専門性があって、その中で能力を発揮して欲しいという意図は英語圏の方が強く感じる。
日本人の友人で日本の大手衣料系会社で店舗管理をやっていた奴がいて、そいつから「今度、人事部に異動することになったんだよ」と聞いてなんかすごいびっくりした。こういうのは英語圏ではあり得ない。店舗管理の専門性と人事の専門性は明らかに違うし、もし人事部に人が足りないのなら、外から人事の専門性を持った人材を採ってくるのが英語圏のやり方だ。社内の人間を別部署に異動させて補うという発想が元から無い。エンジニアについても一緒。「次からSwiftが必要になるから、勉強しとけ」なんて言われたことも言ったこともない。そうなったらただSwiftが今日からできる即戦力ある人を雇うだけ。
そんな背景もあってエンジニアはちょっと専門バカ的になってしまうのだろう。
スタックオーバーフローにあったエンジニアの給与調査で肩書に「フルスタック」の文字が入ると給与が低くなる傾向がある、とデータが出ていた。その理由もなんとなくこの辺りにある気がする。

3)SEがいない
SEは日本だけに存在する職種で英語圏には無い。こちらの記事に詳しいことは書いたが、それぞれのエンジニアが受け持つ範囲が日本で言うところの上流から下流まで全てになる。コーディングの知識が無い人が誰かに「こういう機能を実装しろー」と言って仕事が完結するポジションは無い。なので「エンジニア=専門の範囲内において設計から保守まで全部見ることができる人」となる。ビジネスよりの人が「こういう機能があるべき」と言ったとしてもエンジニアがコード書いている途中に「おいおい、そんなモン要らん。こっちの方がいいんだ。なぜなら。。。」とやってる場面がよくあるし、そうやらなければならない。

要は環境がこんな感じなのでそこで生活するエンジニアもそれに合わせている。当たり前すぎるので全員が英語できる、というのは省略した。(そこが一番大きな違いかも。。。)
日本と海外のエンジニアの「平均的な技術力」に違いはあるか?の回答でした。

ご質問あればぜひこちらから! しばらく質問箱は閉じることにしました。


海外転職の情報収集はこの本がいちおし。書評も書いた。

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com
tango-ruby.hatenablog.com

ReactのRedux非同期処理がサルでも分かる超解説

この記事はかつての私と同じように「Reduxを使った非同期処理がいまいち分かんねー」という方に向けて書いた。とりあえずはReactの公式サイト、Reduxの公式サイトDan氏のReduxビデオ解説を観たが、なんかスッキリしない。特にReduxの非同期処理が分からない、という方向けの超シンプル解説。

Reactは公式サイトのチュートリアルなんかも充実していて丁寧だし分かりやすかった。しかしReduxは違う。特に公式サイトの非同期処理の例が変にややこしい。
こういうことをブログで書くと「アタシは公式サイトの説明を読んでも分からないバカです」と言ってるみたいだから、恥ずかしいしあまり書かれない。ウザいぐらいに「Reduxは素晴らしい。シンプル。カンタン」という発言がネット上にあふれている。

しかし私の頭ではパっと分からなかった。私以外でも「これ難しいなー」と思ってる人が居るんじゃないだろうか。仮に今は分かっていてもそこに達するまでにまーまー苦労したとか。オープンイノベーションの世界では「オレは習得するのに苦労したから、後続の人も同じ苦労をしろ」を根絶するべき。
したがって恥を忍んででも「ReactのRedux非同期処理がサルでも分かる超解説」を書くことにした。

この解説方法を一言で言うとこうなる。

まず先にReduxを使わないで非同期処理のコードをReactで書いて、その後でReduxを加える

これをやることでやっと理解できた。

本記事で最終的にできあがるコードの動くサンプル。
https://simple-example-redux.herokuapp.com/

これは単にサーバーに保存されているコメント群をとってきて表示するだけ。

ソースコード
クローンしてそのまま npm install してnpm startとすれば動きます。まだまだ学習中の身でもあるので「こうした方がいい」とかあったらぜひコメントください。

Reactだけの例

まずはReact だけを使った例
これはReactの基礎知識があれば把握できるレベルの単純なコード。

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class CommentList extends Component {
  constructor() {
    super();
    this.state = {
      comments: [
        {
          id: 1,
          comment: 'comment 1'
        },
        {
          id: 2,
          comment: 'comment 2'
        },
        {
          id: 3,
          comment: 'comment 3'
        },
        {
          id: 4,
          comment: 'comment 4'
        }
      ],
      hasError: false,
      isLoading: false
    }
  }
  render() {
    if (this.state.hasError) {
      return <p>error</p>;
    }
    if (this.state.isLoading) {
      return <p>loading . . . </p>;
    }
    return (
      <ul>
        {this.state.comments.map((item) => (
          <li key={item.id}>
            {item.comment}
          </li>
        ))}
      </ul>
    )
  }
}

ReactDOM.render(
  <CommentList />,
  document.getElementById('app')
)

実行した結果

ソースコードをクローンした場合はコミットログのaacf3a3 "non-redux example"にして、npm startすれば以下の画面が出る。
f:id:tango_ruby:20170626055310p:plain

constructorを見れば分かるようにstateには配列でcommentとboolean形式で2種類のステータスを入れている。

constructor() {
    super();
    this.state = {
      comments: [
        {
          id: 1,
          comment: 'comment 1'
        },
        {
          id: 2,
     // :  省略

      ],
      hasError: false,
      isLoading: false
    }


isLoading かもしくは hasErrorをtrueにすると、それぞれの表示に切り替わる。

APIからデータを取ってくる

ソースコードにcommentsを書き入れるのでは内容が変化しないので、そこをAPIからJSON形式で取ってくるように変更する。

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class CommentList extends Component {
  constructor() {
    super();
    this.state = {
      comments: []
    }
  }
  fetchData(url) {
    this.setState({ isLoading: true });
    fetch(url)
      .then((response) => {
        if (!response.ok) {
          throw Error(response.statusText);
        }
        this.setState({ isLoading: false });
        return response;
      })
      .then((response) => response.json())
      .then((comments) => this.setState({ comments }))
      .catch(() => this.setState({ hasErrored: true }));
  }
  componentDidMount() {
    this.fetchData('https://594ecc215fbb1a00117871a4.mockapi.io/comments');
  }
  render() {
    if (this.state.hasError) {
      return <p>error</p>;
    }
    if (this.state.isLoading) {
      return <p>loading . . . </p>;
    }
    return (
      <ul>
        {this.state.comments.map((item) => (
          <li key={item.id}>
            {item.comment}
          </li>
        ))}
      </ul>
    )
  }
}

ReactDOM.render(
  <CommentList />,
  document.getElementById('app')
)

つまり以下のコードがmount時に実行されてコメントをAPIから取ってくる。

  componentDidMount() {
    this.fetchData('https://594ecc215fbb1a00117871a4.mockapi.io/comments');
  }

https://594ecc215fbb1a00117871a4.mockapi.io/commentsというのは無料で登録したモックで、アクセスすると5つのコメントをJSON形式で返してくる。本来ならここはRailsとかのサーバーにしてお好きなJSONを返すようにする。

動かした結果の画面はほぼ同じだが、コメントの中身はAPIから取ってきてますよ、と。

Reduxを入れる

では上記のコードにReduxを入れていく。まずはredux react-redux redux-thunkが必要になるのでそれらをインストールする。

npm install redux react-redux redux-thunk --save

念のためReduxの3原則
Three Principles · Redux

  • Single source of truth(状態管理は1箇所だけ)
  • State is read-only(状態は読み取り専用)
  • Changes are made with pure functions(変更は純粋な関数で行う)

Reduxを入れてコードが完成した後のファイル構成

├── package.json
└── src
    ├── actions
    │   └── comments.js
    ├── components
    │   └── CommentList.js
    ├── index.html
    ├── index.js
    ├── reducers
    │   ├── comments.js
    │   └── index.js
    └── store
        └── configureStore.js

Stateの内容

Redux無しのコードで明らかになったようにStateには3つのプロパティが必要。comments、hasError、isLoadingでありそれぞれにReduxアクションが必要になる。

src/actions/comments.js

export const getCommentsError = status => ({
  type: 'GET_COMMENTS_ERROR',
  hasError: status
})

export const loadComments = status => ({
  type: 'LOAD_COMMENTS',
  isLoading: status
})

export const fetchCommentsSuccess = comments => ({
  type: 'FETCH_COMMENTS_SUCCESS',
  comments
})

getCommentsErrorとloadCommentsはstatusを引数としてtype とステータスを返す。
fetchCommentsSuccessはデータの取り出しに成功したらコメントを配列に入れてcommentsとしてtype と共に返す。

アクションクリエータはアクションを返す。返すと書いているのにReturnが無い!となった方はこれは以下のように書いてるのと同じ。以下のコードをアロー関数で書いてreturnを省略しただけ。

export function getCommentsError(status){
  return {
    type: 'GET_COMMENTS_ERROR',
    hasError: status
  };
}

アクションとしては元のRedux無しにあったfetchDataに相当するアクションがもうひとつ必要になる。ここではそれをfetchCommentsとして作成する。

src/actions/comments.js

export const fetchComments = url => {
  return (dispatch) => {
    dispatch(loadComments(true));

    fetch(url)
      .then((response) => {
        if(!response.ok) {
          throw Error(response.statusText);
        }
        dispatch(loadComments(false));

        return response;
      })
      .then((response) => response.json())
      .then((comments) => dispatch(fetchCommentsSuccess(comments)))
      .catch(() => dispatch(getCommentsError(true)));
  }
}

reducers

reducersはstateとactionという2つの引数を持つ。reducersの中ではswitchを使ってaction.typeごとに処理を分けて、それぞれのactionを返す。

reducers/comments.js

export const getCommentsError = (state = false, action) => {
  switch (action.type) {
    case 'GET_COMMENTS_ERROR':
      return action.hasError;
    default:
      return state;
  }
}

export const loadComments = (state = false, action) => {
  switch (action.type) {
    case 'LOAD_COMMENTS':
      return action.isLoading;
    default:
      return state;
  }
}

export const comments = (state = [], action) => {
  switch (action.type) {
    case 'FETCH_COMMENTS_SUCCESS':
      return action.comments;
    default:
      return state;
  }
}

それぞれのreducerをrootReducerでくっつける。
importでそれぞれのreducerをインポートする。後はcombineReducersで囲う。
reducer名をもっとシンプルにgetErrorとかloadとかでも良かったんじゃないの?と思うかもしれないが、ここはできるだけcommentsという主語を入れた名前の方がいい。
今回の例では全てのreducerはcommentsに関することだが、これ以降にusers、likes、とか色んなreducerを扱うようになった時に混乱しないため。

reducers/index.js

import { combineReducers } from 'redux';
import { getCommentsError, loadComments, comments } from './comments';

export default combineReducers({
  getCommentsError,
  loadComments,
  comments,
});

Store

ここはほぼ全てのReduxの解説にある内容と同じ。こうしてStore作りますよ、と。
store/configureStore.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';

const configureStore = initialState => {
  return createStore(
    rootReducer,
    initialState,
    applyMiddleware(thunk)
  );
}

export default configureStore


index.js

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import CommentList from './components/CommentList';

const store = configureStore();

render(
  <Provider store={store}>
    <CommentList />
  </Provider>,
  document.getElementById('app')
);

Components

components/CommentList.js

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { fetchComments } from '../actions/comments';

class CommentList extends Component {
  componentDidMount() {
    this.props.fetchData('https://594ecc215fbb1a00117871a4.mockapi.io/comments');
  }

  render() {
    if (this.props.hasError) {
      return <p> error </p>;
    }
    if (this.props.isLoading) {
      return <p> Loading...</p>;
    }

    return (
      <ul>
        {this.props.comments.map((item) => (
          <li key={item.id}>
            {item.comment}
          </li>
        ))}
      </ul>
    );
  }
}

CommentList.propTypes = {
  fetchData: PropTypes.func.isRequired,
  comments: PropTypes.array.isRequired,
  hasError: PropTypes.bool.isRequired,
  isLoading: PropTypes.bool.isRequired
};

const mapStateToProps = state => ({
  comments: state.comments,
  hasError: state.getCommentsError,
  isLoading: state.loadComments
});

const mapDispatchToProps= dispatch => ({
  fetchData: (url) => dispatch(fetchComments(url))
});

export default connect(mapStateToProps, mapDispatchToProps)(CommentList);

まず importしているconnectがcomponentをstore につなげる役割をする。
actionsからはfetchCommentsのみをimport する。ここで必要なのはこのアクションだけで他のはdispachして呼び出す。

後はもう細かい説明よりコード見た方がいい。


JavaScript界隈に足を踏み入れる前に以下の本を読んで、結構参考になった。バリバリのフロントエンドのエンジニアには不要だが、これからフロントエンドも知っておこうかな、ぐらいの人にはちょうどいい内容だった。

いまから始めるWebフロントエンド開発

いまから始めるWebフロントエンド開発


tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

【読者質問 01】海外転職の求人を探した方法

ついに質問きたー!ありがとうございます。とりあえずいただいたご質問は題名を付けて本ブログで回答を載せることにした。題名は

読者質問(+シーケンス番号)

とする。
やっぱり「質問募集!」とやってしばらく来ない日が続くとなんかドキドキする。こうして反応をいただけて素直に嬉しい訳で、しかも質問からいろいろ読み取れることもあるので値千金かな、と。

では、質問。

海外転職の求人を探した方法の実体験を教えていただきたいです。リクルートのような求人サイトを使ったのか、linkedinを使ったのか、友人の知り合いのツテなのか、実際に就職した際に使われてた方法をご教示願えたらと存じます。

この質問を見て、なるほどと思った。今となっては当たり前のように求人情報にアクセスしているが、最初の頃はその求人自体をどこで探せばいいのかすら分からなかったな、と。質問が無ければこんな視点でブログ記事を書くことは絶対に無かっただろう。質問を通してご指摘いただけるのがとてもありがたい。

で、答えだけど、求人は普通にググって探した。

日本からシンガポールへ行った時は海外転職が初めてだったし分からなかったからリクルートみたいな転職エージェントに登録したこともある。しかしアレはあまり役に立たなかった。もっと上手に使えよかったのかもしれないが、それ以降は全て自分でググってヒットしたIT企業に直接CVを送っている。

linkedinももっと活用した方がいいのかもしれないが、転職活動を初めて「そろそろlinkedinも作ろうかな」みたいなタイミングでだいたいオファーをいただいて、作る前に活動終了となるパターンでまだ作っていない。

一番固いのは知り合いのツテ。シンガポールで2度ほどツテで転職面談したことがある。
しかしシンガポールからヨーロッパへ転職活動する際にはそんなにヨーロッパに知り合いが居ないのでツテなんか使えなかった。やっぱりググってヒットさせるのが一番てっとり早くていい。別にすごい手の込んだ検索方法をやっている訳ではない。

検索キーワードは 「自分の技術(JavaとかRubyとか)+地名(London)+Jobs」。もう基本的過ぎてアドバイスにもなっていないみたいだが、素直にググって探したのが実情。

エンジニアとして採用に携わるようになって分かったのはどこから来たかで人材を選別することは絶対に無いということ。候補者が何人か居てそれぞれ、linkedinから、自社のウェブサイトから、転職エージェントから、と来たとして誰かを優遇したりすることは一切無い。求人している企業にとってはどこからでもいいからいい人が来て欲しい、とただそれだけだからだ。
linkedinの経歴が多少キレイに整理されたとしても、エンジニアの場合は「どこで働いてきたか」よりも「なにが作れるのか。なにを作ってきたのか」の方がよほど重要。GitHubや個人プロジェクトを充実させた方がオファーへの近道にはなる。

なのでどこから求人を見つけるか、にはほとんど神経を使っていない。

しいて注意する点としてはどんなに大都市でも求人量には限りがあるということ。ググってヒットした会社全部にやたらめったらCV送りまくって食い散らかすとその内にネタが無くなる。なので検索ヒットした会社をA,B,Cと3段階ぐらいに分けてAが一番入りたい会社群、Cがオファーもらっても行きたくない会社だけど技術バックグラウンドが狙ってるとこと同じ。Bがその中間として、C→B→Aと攻めていくことをおすすめする。

最初はCの会社をサンドバック代わりにしてバンバン面談して練習すればいい。こういう転職面談の練習って英語ネイティブな人でも普通にやってることだし、母語が英語じゃなかったら余計に準備しておかないと失敗してしまう。

ということで「海外転職の求人を探した方法」でした。これからいろいろ質問いただいて、それに答えていくうちに回答能力が上がっていくかも、と考えている。

ご質問あればぜひこちらから! しばらく質問箱は閉じることにしました。


こういう質問形式でコンテンツを充実させる方法は高城剛の黒本、白本を読んで「あーこれだ」と思ったから。

黒本 参

黒本 参

白本 参

白本 参

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com