fish
を使っていて、 eval
の設定方法がわからなかったので調べました。
# evalの設定方法 # bashrc での rbenv の設定 # eval "$(rbenv init -)" と同様のことを書きたい時 eval (rbenv init - | source) # その他の書き方 # eval () ← zshやbashで言うところの $ を付けない # source () 直に指定
などなど
fish
を使っていて、 eval
の設定方法がわからなかったので調べました。
# evalの設定方法 # bashrc での rbenv の設定 # eval "$(rbenv init -)" と同様のことを書きたい時 eval (rbenv init - | source) # その他の書き方 # eval () ← zshやbashで言うところの $ を付けない # source () 直に指定
などなど
オブジェクトの中に意図するプロパティを取り出せるのかを確かめる方法に、オブジェクトが持つメソッドを調査するという方法があります。
rubyではjsonオブジェクトのプロパティもメソッドとして取り出せるので、プロパティ not found
エラーが発生してプログラムの処理が中断してしまわないか予め調べておくことができたら便利です。
例えばある特定のAPIを叩いて返ってきたレスポンスオブジェウトのプロパティを調べるという動作を想定します。
response.methods.grep(/任意のメソッド名 or プロパティ名/)
grepメソッドを使って正規表現で調べればいとするメソッドを調査できます。
methods
で全てのメソッドを取り出さずとも、特定のメソッド(プロパティ)を取り出すことが可能。
※ 上記サンプルでは前方後方一致を使っています。
これは rails console でも使えるので、api叩いて、ちゃんと想定するレスポンスの型になっているかを事前に調査できるので便利だと思う。
もともとのgithubのユーザーネームは ememhr
っていうのを使っていたのですが、これ、前職で会社用のgithub.comを作るために、もともと作っていた個人用のgithubとは別にとったもので、インターネット周りでは全てのユーザーネームを emahiro
で統一しているので、どうしても座りが悪いと感じていました。
もともと ememhr
で作成したアカウントはそこまで運用していくつもりはなかったのですが、気づくと会社用で作ったアカウントがメインのアカウントとして稼働してしまっていて、もともと個人でもっていたアカウントは使わなくなってしまってました。
githubのアカウントが2つあるのも変な話なので、古いアカウントを削除して、空いたユーザーネームに変更しました。
ememhr
で作っていたリポジトリへのアクセスの4つが懸念点として考えられました。
Changing your GitHub username
What happens when I change my username?
のgithubの公式のヘルプページを参照
警告を食らう
古いプロフィールページにはリダイレクトしないよ
ってことが書いてある。
なので問題なくユーザー名を変更します。
この時点で懸念点だった1はクリア。
変更後もOrganizationのアカウントには問題なくアクセスできました。
また、旧ユーザー名でリンクを貼っていたサイトもwantedlyとlinkedinとqiitaくらいだったので、この時点ではそこまで問題にならず。
んで最後のローカルのリポジトリについてですが、個人的に開発したものについてはほとんど更新する必要もなかったので、ほっといても良かったのですが、とりあえず地道に1つずつ新しいユーザー名でのURLに変更
# まず globalのユーザーネームを変更する $ git config --global user.name 'emahiro' # 変更したいリポジトリで $ git remote -v origin https://github.com/ememehr/(repository_name).git (fetch) origin https://github.com/ememehr/(repository_name).git (push) # 新しいユーザーネームの形に変更 $ git remote set-url origin git@github.com:emahiro/(repository_name).git
これでOK。
※1 もともとhttpで設定していたのに、ssh経由に変えた理由は ※2 リポジトリを一つ一つremote先を変更するのはめんどくさいですが、このためにリポジトリを断捨離しておいて良かったです。たまに、githubにあげておいてもしょうがないようなリポジトリは掃除しておくのもいいかもしれません。
これでかねてより取りたかったusernameでgithub生活を送れるようになりました。
goはオブジェクト指向言語とは違って、書いていてC言語を書いている印象に近く、OOPで言うところのクラスが構造体で、クラスにメソッドを定義することは、構造体にメソッドを定義することになる。
しかし、クラスでカプセル化したりするわけではないので、構造体にメソッドを定義する、その定義のしかたがちょっと理解しづらかったのでそのメモ。
goを使って簡易的なHTTPサーバーを立てたときに、http.Handle
にServeHTTPのメソッドを持つ構造体を作るって箇所で詰まって色々調べた備忘録。
package main import ( "fmt" "net/http" ) // AppHandler アプリ type AppHandler struct { appName string } func (h *AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Println(h.appName) } func main() { http.Handle("/", &AppHandler{appName: "myApp"}) if err := http.ListenAndServe(":8080", nil); err != nil { fmt.Println("エラー発生") } fmt.Println("サーバースタート") }
こういうコードを想定。特に最初???だったのはここ
func (h *AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println(h.appName)
}
これは
type AppHandler struct { appName string }
の構造体に ServeHTTP
メソッドを定義している。
https://golang.org/pkg/net/http/#Handler を確認すると、
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Handlerはinterface型なので、定義先はどんな型でもいいのだけれど、ServeHTTPというメソッドを持っていなくてはならない。
つまりAppHandlerの構造体であってもServeHTTPを定義することができる
func (構造体) (登録するfunc) {}
という形で定義が可能。
これでAppHandlerの構造体はServeHTTPというメソッドを持つことができ、http.Handleに登録して、指定したPortでHTTPリクエストを受け付けることができるようになる。
HTTPサーバーを立てる際に以下のような定義の仕方もある。
func main() { http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { res, err := w.Write([]byte(`Test`)) if err != nil { log.Println("Error") } log.Printf("%d", res) })) }
HTTPサーバーを立てる上で行っている動作は直観的でわかりやすいのであるが、HanderFuncメソッドは一体どこからきていた何なのかがわからず。これもドキュメントを確認すると下記のようになっている。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
HandlerFunc
が func(ResponseWriter, *Request)
の関数型として定義されていて、HanderFunc
型に ServeHTTP
が定義されている。これは構造体に ServeHTTP
を定義していたのと似ていると思う。特定のエイリアスを関数型として定義して、その関数型に関数を持たせるという個人的には摩訶不思議な言語仕様のなせる技なんだなーと。
goは言語仕様がシンプルで柔軟というはこういうところから来ているような気がしました。
zshの管理がめんどくさくなってきたのでもう少しイケてるshell環境をつくれないものかと調べてたら、fish
なるものがあるらしく、使い勝手がよさげなので、期間限定で乗り換えてたときの備忘録
1と2もどちらもパッケージ管理ツールに変わりはないので、どちらでも良さげ。
軽く調べたらfisherman
の方がイケてるらしい。
最初に1を入れてその後2を入れたものの、 agnoster
というテーマを使いたかったのに fisherman
では表示がおかしかったので、oh-my-fish
を使用。
fishでの標準的なシェルの設定は、 ~/.config/fish/config.fish
を使用する。
しかし、oh_my_fish
を使うと ~/.config/fish/conf.d/omf.fish
になる。
ただし、ここに書きすぎると後々やっぱり fisherman
使いたいなーとか思った時に足かせになるので、自前で ~/.config/fish/config.fish
を作って、そちらにちまちま設定を書いていった方が無難(のような気がする)
pecoとghqを入れておく。
$ brew install peco $ brew insatll ghq
冗長にbrewで入れようと思ったけど、oh-my-fishを入れると自動的にpecoもghqも入るっぽい。
というわけでoh-my-fishを入れる。
$ curl -L https://get.oh-my.fish | fish
これで omf
コマンドが使えるようになる。
fishではoh-my-fishにしろ、fishermanにしろ、パッケージ管理ツールを使ってテーマ等々shellのカスタマイズを行うツールをインストールする。
例えば、agnoster
というテーマを入れようと思った場合、
$ omf install agnoster
と入力すると、テーマがインストールされて自動的に反映される。インストールしているテーマは ~/.config/omf/theme
のファイルに記録されている。
設定ファイル(config.fish)上で set theme (theme名)
と書いておくことで使いたいテーマを決める。※ 1つしか入っていないと自動的に決まるみたい。
※ 注 : agnosterというテーマはPowerlineというフォントを指定しないと正確にテーマが反映されないので注意
とりあえず今回はここまで。
fishはパッケージ管理の容量で簡単にshellがカスタマイズできるので使い勝手が良さげ。専用のコマンドがあるのもありがたい。
設定ファイルはもう少し使い込んで、もろもろ設定してからまたエントリーにする予定。
package main import ( "fmt" "net/http" ) func main () { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){ w.Writer([]bytes(`表示内容`)) } } if err := http.ListenAndServe(":8080", nil); err ! = nil{ fmt.Printf("エラー表示") }
package main import ( "fmt" "net/http" ) type AppHandler struct{ appName string } func (h *AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Printf(h.appName) } func main () { http.Handler("/", &AppHandler{appName: "myApp"}) if err := http.ListenAndSerce(":8080", nil); err ! = nil { fmt.Printf("エラー発生") } }
exported type AppHandler should have comment or be unexported (golint)
というエラーが発生。これはキャメルケースの構造体(外部からアクセス可能)なのにコメントが無いよっていうエラー
// AppHandler type AppHandler struct{ appName string }
とコメントをつける
comment on exported type AppHandler should be of the form "AppHandler ..." (with optional leading article) (golint)
というLinterのエラーがまだ発生。これはコメントの形式の警告。 構造体名 (半角スペース) 説明
みたいな書き方が必要になる。
// AppHandler アプリケーションに関わる〜 type AppHandler struct{ appName string }
というコメントに説明箇所を半角スペースで追加する。
go言語を学び始めました。
最近専らクライアントサイドの開発がメインだったので、サーバーサイドやりたくなったので、徐々に浸透してきたgoについてキャッチアップはじめました。
環境
エディター
とりあえず自前のPCで動かす環境を簡単に構築します。
$ brew install go $ go version go version go1.8.1 darwin/amd64
goを書くにあたってエディターもろもろ調べましたが、どうやらatomでのgoの開発環境構築がイケている感じを受けたので、atomでgoの開発環境を構築します。
go-plusをどうさせるためにgoに依存パッケージをインストールする必要があります。
※ go-plusのドキュメントに記載してあります。
goではrubyのgemのように簡単にgoのパッケージを管理するインターフェースが用意されています。
$ go get ~
コマンドでgoのパッケージをローカルの環境にインストールできます。
普段は、ドキュメントに書いている順序でコマンド打ち込んでいくのですが、めんどくさいのでshellスクリプトを書いて一括でインストールさせます。
#!/bin/sh #go-delveをbrewで入れる $ brew install go-delve/delve/delve $ go get -u golang.org/x/tools/cmd/goimports $ go get -u golang.org/x/tools/cmd/gorename $ go get -u github.com/golang/lint/golint $ go get -u github.com/sqs/goreturns $ go get -u github.com/nsf/gocode $ go get -u github.com/alecthomas/gometalinter $ go get -u github.com/zmb3/gogetdoc $ go get -u github.com/rogpeppe/godef $ go get -v github.com/golang/lint/golint
完了したら、atomで上記go-def
とgo-plus
の2つのパーケージを入れます。
packageをインストール後、atomを再起動すると、 liter-golint
のパッケージのところでエラーが発生します。
これはGOPATHを設定していなかったことが問題で、GOPATHを設定し、packageの設定画面のGOPATHに設定したGOPATHのパスを入力します。
GOPATHを設定しないと、goでインストールしたライブラリが正常に動作しません。
GOPATHは適当に決めていいらしいので、今回は ~/.go
にGOPATHを設定しました。
設定方法はPATHを通すだけなので、bashrc or zshrc にGOPATHのパスを追加します。
$ emacs .zshrc # GOPATH を追加 export GOPATH=$HOME/.go # シェルを再起動 $ source .zshrc
atomの設定画面から「パッケージ」を選択し、go-plusの設定画面のGOPATH項目、 ~/.go
を入力します。
これでlinter-golintのエラーは解消され、atomでgoを書く上で最低限の設定は完了しました。
GOPATH設定のところで少しハマりましがが、Goの言語仕様を説明した入門本もさくっと読んだので、コツコツ開発始めていこうと思います。
『なぜ、あなたの仕事は終わらないのか スピードが最強の武器である』を読んでみての感想。
新卒時代に、当時の上司から口酸っぱく言われていたことが全て書いてあり、当時の上司はこの著者の言っていることを指して指導してしくれていたのだとここに来て気づきました。
書籍そのものは、厚みの割に文字が大きくさっと読み流せるボリューム感です。
著者が伝えたいことは一冊を通して同じで「スピードが最強の武器である」ってことだけです。
それを色んなエピソードや仕事の仕方を交えて解説しています。
多分読む人によっては拍子抜けするかもしれません。
とは言え、同じエンジニアとして、仕事をする上で改めて大事だと思うことも書いてあったのでまとめてみました。
の2つです。これは著者も色んな言葉で書いていましたが、僕が感じたのは、一冊を通して、ほぼこの内容しか言っていないということです。
多分この2つさえ、ちゃんとできるようになれば、意味不明な飲み会のルールなんか覚えなくてもいいと思います。
この本に書いてあるのは、大事なことは上の2つで、それを常に実践するためにはどうするかのスタンスが書かれているのみです。
中でもしきりに書かれているのは、 仕事全体を2:8に分け、最初の2割の日程で仕事全体の8割を終わらせる ってこと。
このセンテンスは頻出で、それだけ大事なことで、著者である中島さんのキャリアは全て上記のスタンスに集約されているのでしょう。
もちろん、今の立場にあるからこそできる内容(ex 昼寝の仕方 etc...)もあって全てが全て実施できるわけではありませんが、もし、仕事の仕方として自分で納期を切れる、もしくは納期を明確に伝えられている場合は、参考になる内容がかなりあると思います。
とりあえずやってみて、肌感覚掴んでから一旦報告して、納期までに終わりそうか確認する。
とはいえ、これって案外難しくて、やはり任された仕事である以上、1人でやり遂げたいし、周りに相談したり、ましては納期を変更するなんて、自分の無能をさらけ出しているみたいでかっこ悪くてしづらいと思います。
自身にも似たような経験がありますし、それで迷惑かけたこともあります。
でも、結局仕事は自分1人でしているわけではないので、そこはちゃんと伝えるべきなんですよね...(自戒も込めて)
報告が遅くて困ることはありますが、報告が速くて困ることはまずありません。
書籍の内容は平易で、エピソードもなかなかわかりづらいところもありますが、これは社会人歴浅い自分みたいな年代の人にはぜひ知ってほしい内容です。
仕事の仕方は組織や社会のルールに染まる前の方が矯正し易いです。
約束の納期を常に守り続けていれば、自分より高い能力の人間よりも上に行ける。 と触れられている内容の箇所です。
約束を守ること = 信頼 を勝ち取ることであって、スキル上優秀だから信頼できるわけでなく、セルフマネジメントまでできて、常に予定通りに成果を出してくる、または早いタイミングで予定の変更を依頼してくる方の方が、信頼感がある、なんて当たり前のことのようですが、それが実は仕事の全てではないかと。
仕事は結局、人と人の関わりの中で行われることである以上、スキルセットは優秀でも、人として信頼できるかどうかが最終的な意思決定にもっとも効いてきます。
そして、信頼されて任された仕事 = キャリアなので、信頼される人の方がキャリアアップにつながっていく。
もちろん、有無を言わせない天才的なスキルセットを持っている人間はいるので、そういう人間には勝負しても勝てないかもしれませんが、とは言え、そういう一握りの人間と勝負するよりも、凡人同士の中で勝負する中で、いかに相手より信頼されるか、どう差別化して、少しでも上に行くのか、ってことの方が自分にとっては身近な内容で、再現性が高いです。
とは言え、最初の2割の日程の中で普段の10~20倍の生産性で業務にとりかかるなんて凡人には無理ですよね。
著者の中島さんも凡人みたいな事欠かれますが、僕から見たら十分天才の部類です。
なので、実践で活かすとするならば、プロトタイプを作るってところから始めようかなと。
そして、日程の算出の仕方とスケジュールの組み方。
現在、あまり納期が明示されることはありませんが、その場合、仮納期を設定して、どれくらい終わるのかってところ、仮納期を設定して一気に作業することで、自分が躓いているポイント、時間がかかっているポイントを洗い出して、予め潰す、もしくは、部分的に変わってもらう、アドバイスをもらいやすくするなど、色々方法は考えられると思います。
あくまでゴールは時間、納期を守ることで、少しずつ回していくのはその訓練にすぎません。少しずつやっていけばそのうち慣れて行くと思います。
書籍はHowToを謳っていますが、書いてあるのは、即実践で使える具体的なHowToではなく、どちらかというと、スタンスや意識、考え方の側面が強いです。
納期と時間は守るもの、そういう風に最初に決めてしまえばいいのだと思います。
そうすれば、「どうすれば納期を守れるのか▷とりあえずやってみて報告しよう」といった具合に仕事のやり方に行き着きます。
こんな時期なので、フレッシャーズの皆さんには一読してほしい本かと思います。
MacOSX Sierraを2017/04/06時点の最新版にアップデートしたら
$ mysql mysql not found
になってしまった。
$ brew list | grep mysql mysql56 mysql@5.6
入っている…
mysqlの最新版は5.7ですが、現状の業務では5.6を使用していたので、5.6を動作させなければなりません。
※ ちなみにbrew でインストールできるソフトウェアのバージョンは以下で調査できる
$ brew info mysql # インストールできる最新版を表示してくれる。 mysql: stable 5.7.17 (bottled) Open source relational database management system https://dev.mysql.com/doc/refman/5.7/en/ Conflicts with: mariadb, mariadb-connector-c, mysql-cluster, mysql-connector-c, percona-server Not installed
$ brew doctor Your System is ready to brew
問題ない…
$ brew uninstall mysql56 Uninstalling /usr/local/Cellar/mysql@5.6/5.6.35... (345 files, 154.7MB) mysql@5.6 5.6.32, 5.6.34 are still installed. Remove all versions with `brew uninstall --force mysql@5.6`. $ brew install mysql56 brew install mysql56 Updating Homebrew... #中略 Warning: The post-install step did not complete successfully You can try again using `brew postinstall mysql@5.6` ==> Caveats A "/etc/my.cnf" from another install may interfere with a Homebrew-built server starting up correctly. To connect: mysql -uroot This formula is keg-only, which means it was not symlinked into /usr/local. This is an alternate version of another formula. If you need to have this software first in your PATH run: echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc For compilers to find this software you may need to set: LDFLAGS: -L/usr/local/opt/mysql@5.6/lib CPPFLAGS: -I/usr/local/opt/mysql@5.6/include To have launchd start mysql@5.6 now and restart at login: brew services start mysql@5.6 Or, if you don't want/need a background service you can just run: /usr/local/opt/mysql@5.6/bin/mysql.server start ==> Summary 🍺 /usr/local/Cellar/mysql@5.6/5.6.35: 345 files, 154.7MB ➜ ~ which mysql mysql not found
ここで、
To have launchd start mysql@5.6 now and restart at login: brew services start mysql@5.6 Or, if you don't want/need a background service you can just run: /usr/local/opt/mysql@5.6/bin/mysql.server start
パスが /usr/local/opt/mysql@5.6/bin/
以下であることに気づく。
Homebrewで入れた場合は自動的に /usr/local/bin
配下に設置されるものだと理解していたけど、ちょっと違う???
とりあえずパスの在り処がわかったので /usr/local/bin
配下にコマンドをコピーする
$ sudo cp /usr/local/opt/mysql@5.6/bin/mysql /usr/local/bin $ which mysql /usr/local/bin/mysql
と、ここまで来てコマンドの場所がわかったのなら、PATH通せば終わりじゃね?ってことに気づき、 .zshrc
に export PATH=/usr/local/opt/mysql@5.6/bin:$PATH
を通し
$ which mysql /usr/local/opt/mysql@5.6/bin/mysql
と表示されたので完了。
アプリのTableViewから詳細画面に遷移するインターフェースを考えます。
詳細画面に遷移する時に、HTMLをViewに描画するとき、
参考 How to load a HTML string into a WKWebView or UIWebView: loadHTMLString()
let webView = UIWebView() let urlRequest: URLRequest = URLRequest(URL: URL(string: "https://sample.com/XXX")!) // 表示したいページのURL(URLRequest型) webView.loadRequest(urlRequest)
URLを指定して、UIWebViewで表示させるだけなので、簡単ではあるものの、ページの描画に時間がかかってしまう。 そこで、APIのレスポンスでHTMLの文字列が返ってきたような場合、レスポンスで返ってきたHTMLの文字列をHTMLの段組みでWebViewに表示してみます。
QiitaのAPIを例にとって試してみます。
qiitaのAPIのレスポンスは
$ curl https://qiita.com/api/v2/items/97819a1910859a6f2ef9 | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 14941 0 14941 0 0 29195 0 --:--:-- --:--:-- --:--:-- 29181 { "rendered_body": "HTMLの文字列", "coediting": false # 略 }
という形で rendered_body
のプロパティにHTMLの文字列が返ってきます。
この rendered_body
プロパティのHTMLの文字列を取り出してUIWebViewの loadHTMLString
メソッドに渡します。
var webView: UIWebView! let html: String = "rendered_bodyの中身" webView.loadHTMLString(html, baseURL: nil)
これでHTMLの文字列をWebViewに描画することができます。
以下のような感じになります。
今日から新年度ですね。
渋谷ではおそらく新卒入社と呼ばれるみなさんが飲み会をしてました。
賑やかでこの時期ならではの華やかな空気感が色んなところに感じられるようになりました。
自身も新卒で社会に出てから4年目になってしまい、世間的ににはまだまだ若造の部類ですが、業界的にはもうおっさんの域に差し掛かってきたので、目下焦りしかありません。
こんな時期なので今年度はどういったことに意識して技術を学習していこうかなとということを備忘録として記録しておきます。
Web系の言語を中心に1からサービスを立ち上げた経験はあり。
サービスをグロースさせた経験 はなし
新卒で入社した会社でかなり幅広く経験させてもらいましたが、一つの言語に特化して深く理解したことはなし。
言語仕様を完璧に理解する前に、サービスを作りながら徐々に覚えていくタイプ。
年明けからiOSの開発を続けているので、今年はクライアントサイドの開発の知見を深めようと今のところは考えています。
クライアントサイドは、端末の規格が変わったり、プラットフォームの影響をもろに受けるので、実は避けていた分野でした。
ただ、年明けからクライアントサイドの開発に携わる中で、ユーザーに直接触れる領域の良し悪しって露骨にサービスの成長に直結しているなという当たり前のことに気づくようになってきました。
世の中に浸透しているサービスには何か使っている心地よさや、使わないと行けない、使うことが当たり前だよね!といえる感覚があり、また、そういったサービスはアプリだけで完結せず、アプリを中心としたコミュニティというか世界観まで作り上げているものが多く、実装に囚われない、 ソフトウェアだけに収まらないものづくり をしているなという感覚があります。
ユーザーに価値を届けるってソフトウェアだけに留まらない体験を届けることなのではないかと漠然と考えたいことが、ストンと腹落ちしました。
少し話はそれますが、個人的に考えていたことが2つあります。
この2点から、流行りに乗っかったり、今のスキルセットのままいくには漠然とリスクを感じていました。
もともとあるスキルに加えて、ユーザー体験を考えることは、ユーザーの理解、技術の理解、領域の理解、そしてその先にある世界観の理解まで含めて作っていく必要があると思います。
おそらくエンジニアという職業において今後数十年のうちに実際にプログラムを書くことは、人間がやる範囲のことではなくなっていくと思います。
実際、Googleの作っている人口知能はプログラムを書けるようになっているらしいですし、実際にコードを書くということはもはや人間がやらなくてもいいことになりつつあるのかなと。(コード書くのは楽しいですが。)
そんなときに、エンジニアとして何ができるのか、機械に代替できないことはなんなのか、ってことを考えた時、それは体験を作り出し、提供することではないかと一つの仮説を持ちました。
UXを提供する領域、ユーザーに直に触れる領域の知見は今だからこそ身につけるに最適なタイミングなのではないかと。
自分のスキルセットと照らし合わせても、キャリアの幅を増やせるいい機会になると思っています。
今年こそ、何か作りきった!と言えるものを残せるように。
refreshControl
RefreshControlを使ってSwipe & Refreshを実装しようとした際に、 unrecognized selector sent to instance
というエラーが出ました。
selectorに指定したメソッドがないよ
っていうエラーでコードを確認したら、selectorに指定したメソッドがprivate指定してしまっていました。
Selector
で指定できるのは public
なメソッドのみ。
またこのエラーはコンパイルチェックではエラーが出なかったので、実機で動作させるまで気づきませんでした。
refresh control
UIRefreshControl
は 親クラスである UITableViewController
クラスのメンバ変数ですでに宣言されているため、サブクラスでの宣言もオーバーライドもできない。
親クラスに宣言されているので self.refreshControl
で当該ViewContollerでのrefreshControlの実装を編集できる
実装としては以下
override func viewDidLoad() { super.viewDidLoad() // UIRefreshControl self.refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector("publicなメソッド"), for: .valueChanged) self.tableView.addSubview(self.refreshControl!) // viewにUIRefreshControlを追加 }
エリックエバンズの「ドメイン駆動設計」を読む中で、アプリケーション開発に適用する時に感じてきた違和感をまとめてみました。
まだ前半しか読み終えてないので、後半を読んだ際にはまた別のエントリーでまとめようと思います。
大まかにまとめるとアプリケーションは上記の4つのレイヤーに分けられる。
これを一般的なMVC構造を持ったWebフレームワークで考えると
のように大まかに分けることができると思う。
DDDに則れば、
となり、レイヤーに分けることで、責務を分けて凝集度の高い、柔軟なアプリケーションを開発できる。
DDDの古典的名著である本書を読むにあたって、事例等が古く、理解が浅い箇所があったため、精読する中で、一般的に使われているWebFWを念頭に置きながら読み進めていった時にFatControllerやドメインモデル貧血症が発症するのかを考えてました。
理想を言えば全てのエンジニアが本著を読んだ上でアプリケーション開発を進めることができれば、そういった問題も少なくなっていくのではないかとさえ考えていたのですが、開発をしていく中で技術的な負債として溜まっていく課題が生まれる原因を少し考察してみました。
上記2点が自分が考えついたことで、特に後者については、僕自身開発手法や設計手法を知らないまま、エンジニアとしてのキャリアを始めており、WebFW(Laravel/Rails)からアプリケーション開発に入ってしまっているので、ORマッパーが便利過ぎて、Controllerにビジネスロジックをべた書きしたりとかアンチパターンをひたすら繰り返していました。
なぜ厳格にレイヤ分けされていない状態になってしまったのか考えると、Webフレームワークがすでにレイヤー分けされていると考えていて、DDDに基づいたドメインの隔離やレイヤー化を考えなくなってしまっているのではないかと思います。
余談ですが、PHPを使って開発していた頃は、Controllerにビジネスロジックを書くことはアンチパターンだと念頭に置きながら、拙いながらも、責務を分けようとしていたのですが、Railsを使い始めた時にControllerにバリバリActiveRecordを使ったビジネスロジックが書いてあるのを見たときには軽く衝撃を受けたのを覚えています。ActiveRecordは便利で簡潔にデータベースとのやり取りを書けるので、コードとして局所的には見通しが効くものの、DDDの最初の方で提唱しているレイヤー化、ドメインの隔離には反します。
ORマッパーの機能性が高い現在のWebフレームワークでは、こういったControllerにビジネスロジックを書いても、コードの見た目上ある程度見通しが効いてしまうこともレイヤー分けが曖昧になってしまう原因のように感じます。
書籍に戻ると、この書籍が書かれた時期にはまだWebフレームワークそこまでが一般的ではなかったのではないかと思いました。
WebアプリケーションというよりはATMみたいなGUI操作をともなうGUIアプリケーションの開発が念頭におかれているのだと思います。
読み進めていくうちに少なくとも以下の点については書籍を読みながら理解することができました。
そして、自分の感じていた違和感の正体はWebフレームワークにおけるMVCをレイヤー化されたアーキテクチャとして理解してしまっていたことだと気づきました。
DDDの中でサンプルとしてあげられているGUIアプリケーションを開発する際に説明されているレイヤー化アーキテクチャが本来的なMVCで責務が分けれている状態で、WebフレームワークにおけるMVCはそれ自体では厳格にレイヤー化されていないんですね。
インフラレイヤーはまとまっているものの、ドメインモデルの作成は開発者の設計に依存するところが大きい。
Webフレームワークだけしか知らないとドメインの隔離とレイヤー化アーキテクチャという大元を見逃してしまう可能性があるということでした。
※ 自戒をこめて
チーム開発する上で認識甘かったことがあって、最近反省することが多かったので、備忘録として残しておきます。
「開発の前提知識が整わないと生産性高く開発ってできないよね」っていうお話です。
今回反省したのは、技術レベルや実装レベル云々の話ではなく、開発する上で、同じプロトコルで話せないと生産性が上がらないということに気付かされたことです。
rebase
やらsquash
を使って開発ログをきれいするrefs
やらfixes
やらを使いながらissueを交えて作業ログを綺麗にするという基礎的なところから勉強し直し。
慣れればどうってことないんですが、、、
こればっかりは必至に覚えました。
iOS開発のキャッチアップをしているときも、まともに経験のある言語がphpとrubyだったので、swiftみたいな書き方(scalaなんかに似てる)、言語仕様が慣れずにとりあえずプロダクションコード触ろうと思ったんですが、そもそもの言語仕様を理解していなかったり、開発に際して、xcodeの使い方を調べたりということを繰り返して中々生産性が上がってこなかったので、とりあえず地に足つけてひたすらどういう言語なのかを理解することに重きを置きました。
設計やら、実装やらを考えるにあたって、そもそもその前に前提条件が揃ってないと、全く生産性が上がってこない、もしくは開発速度が遅いというのが明確に見えてしまい、そういったところをサボってきた(言語仕様等々は作りながら覚えていく派だった)ツケが回ってきたような気がします。
レビューで言語仕様レベルで指摘されてたらリファクタリングとかしてても終わらないレビューとかになりそうなので、前提知識覚えるのってすごく大事だと実感。
これって、お作法も含めた常識を知っている(同じプロトコルで話ができる)同士のエンジニアが集まると、そもそも話している会話のレイヤーが同じだから開発速度が速いっていうことに直結すると感じました。
言語を知らない、前提が同じでないと、そもそも、何を話されているのかという翻訳が必要になってしまって、そこで脳みそのエネルギーが使われてしまい、そもそも、開発に効率よくエネルギーをかけることができない。
だから、そもそもチームとして生産性高いっていうのは、上記あげたような超初歩的なお作法、バックボーンが同じレベルの人間が集まることで、余計なコミュニケーションが発生することなく、結果として生産性高いっていう状態が生まれるのだなと。
そこに全く行き着いていなかったことを反省しつつ、サボらずに1からちゃんと言語仕様、開発のお作法を直していく日々…
computed property内でforを使った実装をしていた部分をmapを使ったswiftらしい構文へ書き換えました。
変更対象コード
var isSuccess: Bool { for q in questions { if !q.isSuccess { return false } } return true }
変換第一段階は以下
var isSuccess: Bool { questions.map { q in if !q.isSuccess { return false } return true } }
このままだと Ambiguous reference to member map
でxcodeに怒られる
var isSuccess: Bool { questions.map { q -> Bool in if !q.isSuccess { return false } return true } }
mapはクロージャーの中で返り値の方を指定する必要がある。 しかしこれでも返り値はBoolなので、返り値の型が違うのでおこらえる。 mapでの返り値はCollection型で返ってくるので、返り値はBool指定しないと行けない。
そこでやりたかった意図は questionsの回答状況が全てSuccessであればisSuccessがtrue
ということなので、mapで返されたCollectionの中身が全てtrueである、つまりfalseを含まなければいいというロジックになる。
var isSuccess: Bool { let status = questions.map { q -> Bool in if !q.isSuccess { return false } return true } return !status.contains(false) }