君は心理学者なのか?

大学時代に心理学を専攻しなぜかプログラマになった、サイコ(心理学)プログラマかろてんの雑記。

予備校は牢獄だった〜浪人時代予備校にいた「7浪さん」の話〜

f:id:karoten512:20180805163207j:plain

僕は大学に入るまで、3年かかった

3年。長かった。

受験勉強を始めたのが高3の夏。

それなりに勉強をしたのだが結局志望校には入れなかった。

結局僕が入ったのは予備校。そこで1年、そして2年といた。

よく「浪人時代は本当に苦しかった」と言っている人がいて、

「1浪でもつらい、2浪なんて頭がおかしくなる」なんて感想をくれたりするが、

僕は予備校生活が楽しかった。

そう。予備校には本当に色んな人がいた。アメリカもびっくりの人種のサラダボウルだ。

聞こえが良いが、悪くいうとただの魔窟だった。

七浪さん

僕が通っていた予備校には「リフレッシュルーム」と呼ばれているところがあり、

自販機があって、4人座れるでかい丸テーブルがいくつもあって、謎のカウンターがあった。

そこは予備校生が唯一くつろげるオアシス的場所だった。

毎日、大量の英単語・数式・いつ使うかわからない物理法則・その他諸々を詰め込みまくって、

脳が消化不良を起こしてゲロを吐いている、そんな生活を送っているのだ。

死刑囚だって最後はタバコを吸わせてもらえる、

だから予備校生だってリフレッシュしたっていいはずだ。

そんなリフレッシュルームの雰囲気を完全に破壊していたのが

「七浪さん」だった。

七浪さんとその取り巻き

坊主。目にはサングラス。壁を蹴ったら突き刺さりそうなほど先が尖っている靴。

手首には高そうな時計。そして意外に低い身長。

彼はいつも肩を怒らせて歩いているので謎にデカく見えた。

彼の朝は遅い。少し日が傾き始めた15時頃、リフレッシュルームに現れる。

七浪「う〜〜〜〜〜〜〜〜〜〜〜〜〜す

取り巻き達「お勤めご苦労さまッス!!

ここは獄中か?

獄中ヒエラルキー

脱獄モノ映画でみたことある。

長くいるやつほど偉いという獄中ヒエラルキーが、そこに存在していた。

僕は気づいてしまった。

予備校は入学ではない。投獄なのだ。

彼はもう7年も勤めている。懲役何年かしらないがきっともうシャバには戻れないだろう。

3浪や4浪はまだ社会復帰できる可能性が残っている。

僕みたいな1浪はお話にならない。まだ社会に片足を突っ込んでいるただの軽犯罪者だ。

もちろん、素行が悪ければ刑期はもっと伸びる可能性がある。ここではそういうルールなのだ。

彼は勝ち組だった

そんな彼は、リフレッシュルームでいつもポーカーをやっていた。

また彼は既婚だった。子供もいた。

そしてレクサスに乗ってた。

資金はどこから出てるんだと思ったが、親が金持ちらしい。

…酒、女、ギャンブル。

これらすべてが満ち満ちた医学部受験生が、確かに僕の身近に存在していた。

彼はいわゆる「勝ち組」だった。

七浪さんは消えた

七浪さんはセンター試験に近づくに連れ見かけなくなった。

きっと、レクサスを乗り回しているのだろう。

そして時が流れた

センター試験を命からがらこえて、二次試験で頭が真っ白になり、

3点の差で入学を逃した僕はめでたく2浪目に突入した。

授業が終わり、僕はリフレッシュルームにいった。

新入りはどんな面構えをしているのか見るために

長い予備校生活で、僕も完全な「投獄ヒエラルキー」の一員になってしまっていたのだ。

フレッシュな面構えを見ながら、

「生きの良いのが入ってきた…俺達のシゴキにどれだけ耐えられるか見ものだな」

なんてうそぶいていると、全然フレッシュじゃないやつがいた。

そいつは、もちろん坊主だった

命名変更

八浪さんだった。

……こうして彼の名前はincrementされていく。

今もどこかで、静かにincrementされているのかもしれない。

OSSから設計について学ぶ〜クラスの依存方向について〜

要約

僕が間違ってました。すみませんでした。

いきさつ

「Mapを使ったアプリケーションで、Mapの上にMarkerを立てたい」

という要件を満たすために、leafletというOSSを使うことにした。

github.com

  • 公式サイト

leafletjs.com

インターフェース(ライブラリの使い方)を予想してみた

OSSを使うときに、

こんな感じのインターフェースじゃないかなって予想してみた。

僕「MapがMarkerを持ってるから、こうだろ」

f:id:karoten512:20180804220204p:plain

僕「だから、インターフェースはこんな感じだろ」

const map: Map = new Map();
const marker: Marker = new Marker();
map.addMarker(marker);

僕「で、実装はこんな感じだろ」

class Map
{
    private markers: Array<Marker>;
    constructor() {
      this.markers = [];
    }
    addMarker(marker: Marker) {
        this.markers.push(marker)
    }
}

class Marker
{
    constructor() {     
    }
}

僕「うーむ。実に自然な発想だぞ。」

実際のインターフェース

const map: Map = new Map();
const marker: Marker = new Marker();
marker.addTo(map);

僕「え…?」

class Map
{
    constructor() {
    }
}

class Marker
{
    private map: Map
    constructor() {
    }
    addTo(map: Map) {
        this.map = map;
    }
}

僕「え…?え...?」

f:id:karoten512:20180804221839p:plain

Mapクラス「もしかして」

Markerクラス「わたしたち」

僕「(依存方向が)いれかわってる〜〜〜〜!?

他のOSSもみてみた

google map JavaScript API、mapboxGL, mapboxJSなどもみてみたが、

皆同じようなインターフェースだった。

つまり、

MapがMarkerに依存しているのではなく、

MarkerがMapに依存していた。

どうしてこんな設計なのか考えてみた

こっちのほうが変更に強いからだった。

変更に強いとは

例えば、MapにMarkerじゃなくてPopupも追加しようと思ったら、

僕が考えた実装方法だったらこうなる。

※ 理解を簡単にするため、ソースコードは極限まで単純化しています

f:id:karoten512:20180804223242p:plain

class Map
{
    private markers: Array<Marker>;
    private popups: Array<Popup>;
    constructor() {
        this.markers = [];
        this.popups = []; // 追加
    }
    addMarker(marker: Marker) {
        this.markers.push(marker)
    }
    // 追加
    addPopup(popup: Popup) {
       this.popups.push(popup)
    }
}

// 追加
class Popup {
    constructor() {        
    }
}

つまり、Popupの機能を追加しようとしたときに、

Mapの方も修正が必要になってくる。

さらにOverlay(地図上に何かを覆い隠す)を機能として追加してみると、、、

f:id:karoten512:20180804223708p:plain

いい感じに「やな感じ」がしてきましたね笑

やな感じの正体

新たな機能を追加するたびに、Mapの方にも修正が発生してしまっているというのが、

やな感じの正体です。

これは「開放・閉鎖の法則」に違反しています。

開放/閉鎖原則に沿ったソフトウェアは、既存のソースコードを変更せずに機能修正や機能追加を行うことができる。 そのため、品質検査を再実行する必要がない。(wiki)

また、MapとMarkerだけ使いたいときもあるはずです。

そのときも、MapはPopupやOverlayのことまで知っている必要がある、というのもおかしな話ですね。

依存関係を逆転してみる

f:id:karoten512:20180804224309p:plain

class Map
{
    constructor() {
    }
}

// 追加
class Popup
{
    private map: Map
    constructor() {
    }
    addTo(map: Map) {
        this.map = map;
    }
}

既存のコードに手を加えずに、機能を追加することができます。

すごい。すごいね。

どんなときに応用できるか

たとえば、ペイントのようなアプリをつくるとしましょう。

f:id:karoten512:20180804224540p:plain

機能をボンボコ追加しても、修正をしても他のクラスには影響がでません。

素敵ですね。

まとめ

依存関係を逆転して考えると、

その後の機能追加時のコストに大きな差が出てきそうです。

そんなことを、今回知りました。

ただ、今回本当に単純化しているので、実際はもうちょっと複雑なはず。

注意したいです。

本当にオブジェクト指向を使う必要があるのか?

タイトルについて

すみません、煽りました。

いきさつ

オブジェクト指向で設計した時と、

オブジェクト指向を使わなかった時について、

「単純にコード量だけで」比較してみます。

その上で、オブジェクト指向の利点について再度考えてみます。

つくるもの

  • 1秒間に1, 数字がカウントアップされる
  • カウントアップされるたびに、2で割ったあまりと3で割ったあまりが表示される

f:id:karoten512:20180727001417g:plain

コード

NOTオブジェクト指向でかく

const view1Input = document.getElementById("view1Input");
const view2Input = document.getElementById("view2Input");
const view3Input = document.getElementById("view3Input");

var count = 0;
const countUp = () => {
  count++;
  view1Input.value = count % 2;
  view2Input.value = count % 3;
  view3Input.value = count;
};
setInterval(countUp, 1000);

オブジェクト指向で設計して書く

  • input要素の書き換え…Viewクラス
  • カウンター値保持...Modelクラス
  • 処理開始...Mainクラス

という簡単な責務で考えます。 そしてとりあえずobserverパターンで実装します。

すると。

class ViewModCounter {
  constructor(el, model) {
    this.inputEl = el;
    this.model = model;
    this.mod = 1;
  }
  setMod(num) {
    this.mod = num;
  }
  render() {
    this.inputEl.value = this.model.count % this.mod;
  }
}

class ViewCounter {
  constructor(el, model) {
    this.inputEl = el;
    // viewはmodelに依存している
    this.model = model;
  }
  render() {
    this.inputEl.value = this.model.count;
  }
}

class Model {
  constructor(count) {
    this.count = count;
    this.listeners = [];
  }
  setCount(count) {
    this.count = count;
    this.notify();
  }
  addListener(listener) {
    this.listeners.push(listener);
  }
  notify() {
    this.listeners.forEach((listener, index, listeners) => {
      listener.render();
    });
  }
}

class Main {
  constructor() {
    this.model = new Model(1);

    const view1Input = document.getElementById("view1Input");
    const view2Input = document.getElementById("view2Input");
    const view3Input = document.getElementById("view3Input");

    let view1 = new ViewModCounter(view1Input, this.model);
    view1.setMod(2);
    let view2 = new ViewModCounter(view2Input, this.model);
    view2.setMod(3);
    let viewCounter = new ViewCounter(view3Input, this.model);

    this.model.addListener(view1);
    this.model.addListener(view2);
    this.model.addListener(viewCounter);
  }
  run() {
    const model = this.model;
    let sec = 0;
    setInterval(() => {
      sec++;
      model.setCount(sec);
    }, 1000);
  }
}

const main = new Main();
main.run();

めっちゃ増えた

記述量だけみると、オブジェクト指向で設計するほうが大変です。

※ もちろん要件によっては記述量が逆転することもありえます

じゃあいつオブジェクト指向設計を使うのか

個人的には、

  • 要件が複雑になった時
  • 扱うモノが増えた時

この時、オブジェクト指向設計じゃないとかなり苦しむな、と感じています。 あとは保守のときですね。きちんと設計しないと一つの修正がいろんなところに飛んだりします。

注意

オブジェクト指向設計とはクラス化することではありません。

あくまでも、オブジェクト指向は手段で、

目的は「実装時にいかに楽に、そして保守時にいかに楽になれるか」というところにあります。

それを意識せずにただただクラス化するだけだと、

逆に辛くなることだって起こりえます。

(辛くなったことがある)

そこらへんの話がこの本にかいてあります(もっかい読みたい)

https://www.amazon.co.jp/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E8%A8%AD%E8%A8%88%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-Ruby%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-%E9%80%B2%E5%8C%96%E3%81%97%E3%81%A4%E3%81%A5%E3%81%91%E3%82%8B%E6%9F%94%E8%BB%9F%E3%81%AA%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%82%B2%E3%81%A6%E6%96%B9-Sandi-Metz/dp/477418361X

DDDのリポジトリについてちょっとしらべた

f:id:karoten512:20180722231817p:plain

いきさつ

昔書いていたアプリケーションがあまりにあまりなので、

(モデルの責務がいたるところに染み出している)

DDD使って書き換えられないか検討するために勉強を始めた(1日目)

Repositoryからやっていきます

Repositoryについて

エンティティの永続化を担当する。

あと、エンティティの検索を担当する。

使われているところ&メリット

主にServiceクラスで使用されている。

ただし、Serviceクラスが依存しているのはRepositoryクラスそのものではなく、

Repositoryのinterfaceに依存している。

<?php
class UserService {
    private $_userRepository;
    public function __constructor(IUserRepository $userRepository) {
        $this->_userRepository = $userRepository;
    }
} 

なので、もし別のリポジトリを使うことになってもServiceクラスを書き換える必要はない。

つまり、ORMを別のものに替えたり、もっと簡易なクエリビルダ的なものにしたりすることができる。

感想

そもそも今までActiveRecordパターンを使ったフレームワークRails, FuelPHPのORM)での開発がほとんどで、

永続化したり検索したりする部分も"Model"でやっていたので新鮮。

ただ、まだちゃんとしたメリットまではわかっていない。

これから勉強していく。

JavaScriptのgetter, setterについて復習してみた

getter, setterとは

オブジェクトに値を代入したり、

参照したりする時に呼ばれる関数のこと。

書き方

set {プロパティ名}(value) {
  // 処理
}
get {プロパティ名}() {
  // 処理
}

のように書く

実例

var obj = {
    set value(val) {
        this._value = val + 1;
    },
    get value() {
        return 'getterからは' + this._value + 'を返す' 
    }
}
obj.value = 1; // setterが呼ばれる
console.log(obj.value); // getterからは2を返す ←getterが呼ばれている

用途

値をセットしたり、値を参照したりする時、

決まった処理をする際に便利。

json-serverが鬼のように便利で、しかも可愛かった

f:id:karoten512:20180512165110p:plain

いきさつ

APIのmockがほしかった。

今までexpressとかで実際にAPIをつくっていたのだが、

ちょっとめんどくさくなってきた。

何かいい方法がないかな〜と思ってたら「json-server」という良さげなものがあったので試してみた。

github.com

install

npm install -g json-server

jsonデータを用意

db.jsonというファイルを用意する。

{
  todos: [
    { id: 1, content: aaaa, done: false },
    { id: 2, content: bbb, done: true },
    { id: 3, content: ccc, done: true }
  ]
}

json-serverを起動

db.jsonというファイルを同ディレクトリ内に用意。

json-server --watch db.json

f:id:karoten512:20180512164253p:plain

絵文字がかわいい。

localhost:3000にアクセス

f:id:karoten512:20180512164428p:plain

こっちもかわいいかよ。

localhost:3000/todos/1 にアクセス

{
  "id": 1,
  "content": "aaaa",
  "done": false
}

しっかり返ってきてる。

まとめ

GETしか試していないが、

ドキュメントに

GET    /posts
GET    /posts/1
POST   /posts
PUT    /posts/1
PATCH  /posts/1
DELETE /posts/1

とあるのでいろいろ使えそう。

あとかわいい(重要)

【突然の沈黙】JavaScriptのobjectスプレッド演算子について

いきさつ

ソースコードを読んでいたら、

var arr = [1, 2, 3]
var arr2 = [4, 5, 6]
arr.push(...arr2)

とつぜん処理の中で沈黙(...)し始めた。

f:id:karoten512:20180511220526j:plain

気持ち悪かったので調べてみた。

ドキュメントを見てみる

スプレッド構文を使うと、関数呼び出しでは 0 個以上の引数として、 Array リテラルでは 0 個以上の要素として、 Object リテラルでは 0 個以上の key-value のペアとして、 Array や String などの iterable オブジェクトをその場で展開します。

うーん。

わかるようなわからんような。

実際に試してみよう。

...Array, ...Stringはどうなるのか

...Array だと

var arr = [1,2,3]
console.log(...arr) // 1 2 3

ばらばらになるイメージ?

...Stringだと

var str = 'hello world'
console.log(...str) // h e l l o   w o r l d

やはりばらばらになる。

ばらばらにしたものを、何かに渡してみる

関数呼出しに渡すその1

ためしに、...arrを3つの引数を受け取る関数に渡してみる。

var arr = [1, 2, 3]

function add(x, y, z) {
  return x + y + z
}

console.log(add(...arr)) // 6

なるほど。たしかに、

関数呼出しでは3つの引数として展開してくれている。

関数呼出しに渡すその2

この2つの配列をマージするケースを考えてみる。

var arr = [1,2,3]
var arr2 = [4,5,6]

arr.push(arr2)
console.log(arr) // [1, 2, 3, Array(3)]

普通にpushで書くとマージできない(当たり前だけど)

これじゃない。これがやりたいんじゃない。

ここで、

objectスプレッド演算子を使って展開した値を、

配列が持っている関数、pushに渡してみる。

var arr = [1,2,3]
var arr2 = [4,5,6]

arr.push(...arr2)
console.log(arr) // [1, 2, 3, 4, 5, 6]

おお。たしかに関数呼出し内で引数として展開されていることが確認できる。

なるほどねぇ。

配列に渡してみる

var str = 'hello world'
console.log([...str]) //  ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]

おー。確かに配列内で要素として展開されている。

これでsplitを使わなくても良くなるな。

まとめ

objectスプレッド演算子を使うと「ばらばらになる」

そのばらばらになったものを、関数に渡したり配列に渡したり、

Objectに渡したり(まだ試してないけど)することにより、

処理の記述が簡単になる。