Ich habe hunger

あふりかエンジニア、アフリカ向けのB2BのSaaSを開発する

めっちゃでかいリリース(の作業)が終わった

昨年のこの時期もリリース前でバタバタしていた。この1年で結構でかい新規機能投入を含むリリースを少なくとも6回はしている。それでも、まだまだ機能が足りない、ここがバグがある、通信が遅い、データの同期の問題がある、などなど仕方ないものも含め、お客さんに提供は出来ているものの、僕ら自身が目指す世界を実現するためにはソフトウェアとしてまだまだだった。

さっき、ここ1年の中で一番大きいリリースを終えたばかり。日本は夜中の2時だけど、アフリカだと20時頃。ログは全く動いてなかったし、日本とは違って土曜の夜にもなると業務アプリケーションいじってる人は誰もいないんだなーとか思いながら、Enterを押して、アプリケーションのデプロイにDBのmigrationを走らせて、必要なrakeタスクも実行してデプロイ完了。

今回のリリースは、ホントにハードだった。事の発端は4-5月ぐらいで、結構なハイペースでリリースを繰り返していたがアフリカの通信の状況、現地スタッフのアプリケーションの想定のしない使い方、通信が遅過ぎてどうにもならないとか、ソフトウェアとして限界を迎えようとしていた。毎日起こる問題にデバッグをして再現性を見つけて潰しては、また新たなissueが発見される・・・。1年弱走り続けて負債もたくさんあるし、継ぎ接ぎで作って来たアプリケーションなので仕方ないと言ってはいけないのかもしれないが、仕方ないことだと思ったし、いつかどうにかしないといけないけども日に日に積まれていく新機能の提案に対して立ち止まって負債を返済するなんてなかなか意思決定はできなかった。

5月に入ったぐらいかな。代表と話していて、結局ソフトウェア的にはゼロから再度開発をして、必要であればDBや通信を扱うライブラリごと変えてしまうぐらいのことが必要だと思う、という話をして、一旦新規機能の開発や営業を止めてもらって、ゼロから開発し直すことになった。本当に、あのタイミングで良かったと思うし、代表の胆力を見た気がした。カッコいいよね。

そこから、データの同期のアルゴリズムを試行したり、DBのライブラリとの相性を試したりと色々しながらまた新機能も載せながら、かれこれ3-4ヶ月もかかってしまったけど良い物が出来たと思う。ホント、みんなありがとう。Web側も、今回ので色々な機能が乗っかったし、複雑さも増したけども、テストもある程度書けるようになってきたし、それによる恩恵も受け始めてるので、完璧であることは求めないけども、勘所は押さえつつ開発を進めていけたら良いなぁ。とはいえ、やや開発者としてレベルアップが求められている感じがしているので、色んなことを吸収して組織に戻して、さらにそれを共有してとSECIモデルを良い感じに回して行きたいよね。

「あのタイミングでの意思決定で良かった!」と言えるようにするには、ソフトウェアだけではないけども、そう言えるだけの材料は揃ったと思うので、ここからはエンジニアも含めてどんどんと事業を進めていって、ふと後ろを振り返った時にそう言えるだけの結果を出せるように頑張っていこう。

Develop the future of Africa with technology and strategic management

あふりか童貞エンジニア、あふりかに行く。

アフリカインキュベーターにジョインしてから1年ちょっと。今更ながら、アフリカに2週間行ってきました。もともと事業を始めたウガンダカンパラと今年に入ってオフィスを開いたケニアのナイロビにそれぞれ1週間ずつでした。

ずっとリモートで日本からソフトウェアの開発をしているので、どうしてもお客さんの顔も見えないし、どういう人がどういう風に触ってどういう風に使っているかというのも、現地にいるメンバーから聞くというふうになってしまうのでなかなか意図を掴みきれなかったり難しいことがありました(とは言っても、現地にいるメンバーが優秀なので助かってます)。というのもあって、今回アフリカに行くことになりました。後、「お前は本当にアフリカで使われるソフトウェアを愛せるのか」という副題もありました(これはまた別記事で書こう)。

カタール航空で成田からドーハ経由でナイロビ(ケニア)までだいたい丸1日(24時間弱)かけて行きました。早いと18時間ぐらいなんですけどね。この辺は、航空券の安さと乗り換えのスムーズさによるところではあります。日程的には、初めにナイロビに入って1週間。その後、LCCウガンダカンパラに移動して1週間の合計2週間です。

初めの1週間はナイロビで代表とお客さんとこ回ったりミーティングに参加したりしてました。ナイロビではアプリを使う現場の人とは会えなかったのですが、マネージャーの人や経営者の方と会ってどういうところに課題があるかとかどういうことを期待してるかなどを聞けて良かったです。

僕の移動していた範囲がそうなんだとは思いますが、道路も綺麗だしビルも結構あるし、建設中の建物とかもたくさんあってすごく伸びてるんだなーという感じがしました。その一方で、やっぱり裏道に入ったりするとちょっと危険そうな感じがするし、まだまだ貧富の差とかも結構あるんだなって思いました。やはり、ナイロビは危険らしく基本的にUberで移動してました。めっちゃ便利!でも、地図読めないのかだいたい電話して「○○センターの前だよ」とか教えないといけないのはめんどくさい。

ケニアでのご飯は、代表含む現地メンバーが良しとするレストランは全部美味しかったし高くないし、オフィス兼住居での自炊をしてたこともあって全然困りませんでした。大きいスーパーとかに行くとアジアンなコーナーもあるし、ある程度お金さえ出せば生活でストレスを感じることなく生きていけそうだなって思いました。ただ、街中の渋滞だけは勘弁してほしいけど、これは他の国でも同じように抱えている問題だろうしどうしようもないのかなーって感じでした。

着いてから1週間後にナイロビからウガンダカンパラへ移動。
ウガンダの方にはだいたい5人ぐらいのメンバーが居て、あとウガンダ人スタッフが2人。1年間で大きくなったなーという謎の感動をしてました。ウガンダでも、お客さんのとこに同行して話を聞いたりしてたんですが、こちらもマネージャーとかも居たんですが、最終日には実際にアプリ使ってるユーザー(営業スタッフ)と話せる機会があったので大きな収穫。ウガンダで話を聞いててびっくりしたのが、結構な頻度で従業員が入れ替わること。僕らのアプリを使ってもらうために導入の時とかはスタッフが行って使い方とかを教えたりしてるんですが、それを受けた従業員がいなくなったり、営業スタッフが上手くアプリを使ってくれるように面倒を見てくれてたリーダー、マネージャーのような人が居なくなったり・・・とこういうことが結構起こったりするので、より簡単でより使いやすいものを作ることがかなり求められているよなーとより実感しました。当たり前でしょ!と思われるかもしれないですが、スマホというものに初めて出会うような人も結構まだいるそうなので、スマホ慣れしたユーザーでない人にも直観的に使えるようにしないといけないので、これは結構難しいし課題かなーと思います。

あとね、すごく難しいなーって思うのが、徐々にスマホが広がりつつあるも、あんまり良いモデルじゃないとバッテリーが持たない、とか自分でインターネットを100MBとかの単位で購入してたり、インターネットが激おそだったり、そもそも入らなかったり、GPSが精度悪かったり、もーーーーアプリケーションに行く前に色んな課題は山積みだったりします。なので、カスタマーサポートとかでも「えー?そんな質問まで来るんですか笑」みたいなのもあるけど、それも丸っと仕方ないけどそれを包容してなお使いやすいようなものを作れたら最強じゃない?なんて思ったりします。


ウガンダでは、夜はUberを使うけど、昼はだいたいボダ(ボダボダ?)というバイクタクシーを使ってました。ナイロビよりも車移動すると渋滞にひっかかるので、バイクだとその隙間をすいすいすいーっと行ってくれるので結構楽でした。ただ、事故ったら死ぬよなみたいなスピードを出してくれるのでたまにちょっと怖くなりますw料金は結構安いけど、自分で交渉しないといけないのでそこら辺はちょっと面倒だしどうしても外国人なので高めに言われたりするっぽいです(多分)。


この2週間色んなお客さんのとこ行ったり、こっちで人を見たりしてて思ったのは、ウガンダとかケニア特有の問題と途上国特有の問題とがあるのかもなーと思いました。後者の方は、昔の日本だったり他の国でもあるような組織の問題とか雇用の問題とかに結びついてるのかもなーと思っているのでここら辺は時間のある時に調べたりしていきたい。

Anyway, 僕らの作ってる物がいきなり必ずまるごとフィットするわけでもないし、会社によってはコストとして高いのかもしれないけど、この地域で、国で、恐らく必要とされてるもの(シーズもニーズも)を作っているし、少しずつそこに向かっていってるんだろうな、とすごく感じた。このソフトウェアをしっかりと良い物にして、まずは期待してくれてるお客さんを満足させて、口コミで良かったよ、というのが広まるぐらいにしたいな、と思うし出来そうなイメージは出来る。ふたつの国をまたいでいるというだけでもしんどいし、これからどんどんと国の中でも地域にも広がっていったりして僕らはより大変になるけど、それでも売りたい、と思ってもらえる物を作りたいし作れる組織を作るのが僕の仕事だし、その上で色んな人から使いたい、と思ってもらえるものを考えられる、作れる、届けられる、組織にしたいな、と思った。

今回はすごく短かったし、全然現地のことも分からないままにうろうろして、そこまでローカルに染まれずに帰ってしまうぐらい薄い滞在っぽいけども、得たものとしては、未だ小さな物かもしれないけどこれから少しずつ大きくなっていきそうな感じ。法人化して1年、色々がむしゃらに進んで来た1年だしこれからもそうかもしれないけど、後ろを振り返るとしっかり道は出来てるし、これから進めそうな道もうっすらとではありますが見えて来てる感じはします。とはいえ、今のメンバーだけではすぐに限界が来ると思いますので色んな人に頼ってお願いして、一緒に何かできたら良いな、と思ってますので僕から助けを求められたらスッと助けてくれると非常に嬉しいし、限られたリソースかもしんないけど「何かやろうか?」なんて声を掛けてくれると感謝感謝です。僕の会員権使って美味い肉を個室とか割安で行きましょう(僕が行きたいだけなんですが)

刺激的で最高に楽しい滞在の1割も書けてない気はしますが、ここで発散しておかないとこの感覚は薄まっていくので、一旦これで。

とりあえず、これからのAfri-incに乞うご期待ということでより一層励んでいきますので、どうぞよろしくお願いします₍⁽⁽(ી(*´ω`*)ʃ)₎₎⁾⁾

Coffeescript + jQueryでeachする時

なんだか忘れて検索してしまったので備忘録として。

  $('#fillToComplete').click =>
    $('input.fields').each ->
      $(@).val($(@).data('data-value'))

そうだそうだ、@を使うんだった。
久しぶり過ぎて忘れてた。

先生の言った「それで本当に幸せになるのか」という問い

この前、大学の先生のところに久しぶりに挨拶をしに行った。


先生には、研究室で大学3年生〜修士1年生までの3年間お世話になったが、この研究室の先生、諸先輩方と同級生・後輩のおかげで、大学院を中退してもなお社会で何とか今までそれほど困らず4年ぐらい社会人をやっている(ように思われる)。大学院を中退したのは逃げでもあったし攻めでもあったので、たかだか後1年行けば修了する修士課程をわざわざ中退してまで選んで踏み出した道を自分がどう築いて築かれて歩んでいるのか、ということを少なくともお世話になった先生には報告する義務があると勝手ながらに思い、折りを見ては先生のところに足を運んでいる。


この前、先生のところに行った時には、アフリカの話をした。具体的な事業の話も。そこで先生は、「インターネットが広がって情報を得る環境が出来ていく流れに逆行することはもはやできないけど、それは果たして人々を幸せにするのか」と発して黙った。多分、アフリカという我々よりもインターネットを手にするのが遅かったところに徐々に、急速に広がっていくということを想像してのことだと思うが、結局のところ思考としては人間に帰って来たんだろう。


これは非常に面白い問いでずっと考え続けている。多分どこかで散々話されているんだろうけども。それを探して見て理解するのは簡単なのかもしれないけど、研究室で得た問いは自分で納得できるまで考えないといけない気がする。


インターネットがあることで、情報やお金に対して物理的な制約がなくなっていく感じになっていること。先の問いで話していたのは、今まで得なかった情報を得ることで逆に不幸になる人も居るのではないか、ということとお金の流れなども広い商圏に飲まれることで今まで悩まなくてよかったことに悩んだり、成り立たなくなる商売や生活できない人が出てくるのでは、ということ。これは享受できるものと比べると全体の中では小さなものかもしれないけど、それを無視は出来ないかも。


インターネットがあること(世界)で何が起こるかというと、今までなかった情報に触れられることができて、その結果今まで見えてた選択肢以外も見えるし、今まで見えていた結果以外も見えるようになる。


例えば、田舎で就職して、そのまま近所のじいさんばあさんのようになっていく、というようなものしか見えてなかったものが、実は東京では○○で、じいちゃんばあちゃんになっても○○をしている人生のようなものにアクセスが出来てインプットできてしまうということ。こういう話は、本の中では今までもあったとは思うんだけど、普通の人が発信できるメディアがあることで"普通の人"の話としてインプットされてしまう。その結果、空想や消費する物語ではなく、現実味を帯びた話として認識をしてしまう人が居て、その結果その人の中では選択肢が増えてしまう。さらに、その人はその選択肢に惹かれながらも一歩を踏み出すことが出来なく、隣の誰々さんが踏み出したりして、さらに幸せそうな話を聞いてしまったら、もう大変。嫉妬の嵐や自分の中でモヤモヤとしたものが生まれてしまう。なんでその一歩を踏み出せなかったのか。例えばここでいう一歩が東京に行って働くことだとして、その一歩で変わってしまう自分の人生に対して責任を持つ覚悟が出来なかったから。東京に行っても働き口がなかったらどうしよう、結婚は出来るのか、一生東京で住むのか、将来はどうなってしまうのか。人によって色んなハードルはあるし覚悟の一言では言い表せないかもしれないが、何かが一歩を踏み出させなかった。インターネットの話じゃなくなってしまった。


こういう風に考えた時に、じゃあどうやったら一歩を踏み出せたのか、というと想定した"自分の人生に対する責任"というのが小さくなれば良いのかもしれない、と思う。つまり、何かを選択はするんだけどそれによって変わる環境の変化というのは大したことない、と思えるような個人の環境、もしくは社会であれば良いのかもしれない。個人の環境で言うと、恵まれた友人やコネや人脈もそうだし、持ち家や資産もそうだし、もしくは鋼のメンタルみたいなのも個人の環境かもしれない。社会としては、生きるコストが下がる、というのがすごく良いのかもしれなくて、食費が全然かからないとか駆け込み寺じゃないけど住居は何とか確保できるとか。
この辺は、個人的には野菜工場とか農業や畜産業のIT化や輸送や調理みたいなところもどんどんIT化されていくことでコストが落ちてくれると助かるな、と期待してる。うちの大学も学産学消とか言って野菜工場で作った野菜でファーストフードと提携したりしてるし、この辺の技術はどんどん進むだろうし後は法律で企業が参入しやすくなっていってほしい。


この先には人間が働かなくて良い世界が来るのではないかと思っている、というか思いたい。全ての人間ではないにせよ、働かなくても、もしくは少ない労働量で衣食住をある程度満たされる未来は待っていると思う。今の世界はそこに向かうための過渡期であって、今見えてるような世界でそのまま進んでいくのではないだろう。そうした時にぼーっとしているのか、それとも何かをするのか。


僕らは、東アフリカで流通に特化したソフトウェアを作っていて使ってもらっている。僕らが社会に大きくインパクトを与える頃には、色んな人がいつでも色んなものを手に取れるようになるだろうし、もっとモノの行き来というのも進むだろうし、その頃には一部をドローンでキオスクまで運んでいるかもしれない。そうこうしているうちに、発注担当はAIになるかもしれないし、お金もだいたいM-PESAでデジタルでやり取りをしているかもしれない。そういった時代が来た時には、僕らはどんどんと人間がやって来たことをピンポイントにITに置き換えていくことが出来るだろう。もしかしたら、知らず知らずにそういう準備をしているのが僕らの事業なのかもしれない。


「それで本当に幸せになるのか」


改めてこの問いに立ち戻ると、受発注をしていた人は職をなくしてしまうかもしれない。今まで100人雇っていたところを60人で同じことが出来るようになってしまうかもしれない。それでもなお、僕らが現地に行き、色んな事業者と話し、ソフトウェアを開発し、効率化して流通というインフラに関わっていくのか、と考えると、いつか来る未来のために現在を加速させているのかな、と思えるようになった。もちろん、僕ら以外の世界が大きく変わることが前提だし僕らが遅過ぎることもあるだろうけど。そうして、必要最低限以上働かなくて良くなったら好きなことをして生きていければ良いな、と思う。


「それで本当に幸せになるのか」


「分からないけど、加速させて早く到達したい未来はある」

と今は答えることができそう。
5秒後にはまた新しい問いが出て来てまた頭を悩ませることになりそうだけども。

Node.js + expressで色々ログを自前でDBに残そうと思った時の話

タイトルの通りなんですがログを自前で残そうという仕事が発生しました。
具体的には、どんどんデータ操作は業務に沿ってされるべきなんだけども、やべー時(悪意を持った人間が何かをしたとき)とかに「誰が、いつ、どういうデータに対して、どういう操作をしたか」という、まぁよく言うアクセスログをDBに貯めましょう、という話になりました。

で、とりあえずテーブル定義。

CREATE TABLE `log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `user_id` bigint(20) DEFAULT NULL,
  `url` text NOT NULL,
  `method` varchar(255) DEFAULT NULL,
  `request_body` longtext,
  `request_query` longtext,
  `referer` text,
  `session_id` text NOT NULL,
  `user_agent` text NOT NULL,
  PRIMARY KEY (`id`,`created_at`),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;


main.jsの中で呼び出して、ミドルウェアとして登録(この表現合ってるのか?)

var originalLogger = require('./lib/logger');
// 中略
app.use(originalLogger);


ほんでもって、Logger自体の中身なんですが基本的には req.headers の中に含まれてるんですが req.get で呼び出せる模様。
ログインシステムがある時は、ログインしているユーザーのuser_idも取っとくと便利です。
あとは、POSTとかPUTで投げられるパラメーターはreq.body, GETのパラメーターはreq.queryに入ってるのでそれぞれきちんと分ける(特に今回のシステムだとGETパラメーターによって振る舞いを変える部分がある)。
bodyやqueryは連想配列になってるのでそれらはJSON.stringifyでDBに入れられる形に変えておく。

module.exports = function(req, res, next) {
    var user_id = req.session.user_id || null;
    var url = req.url;
    var method = req.method;
    var tmp = [req.body];
    var request_body = {};
    for (variable in req.body) {
        if (variable == 'password'){
            continue;
        }
        request_body[variable] = req.body[variable];
    }
    request_body = JSON.stringify(request_body) || null;
    var request_query = JSON.stringify(req.query) || null;
    var referer = req.get('referer') || null;
    var session_id = req.sessionID;
    var userAgent = req.get('User-Agent');

    // この辺で何か必要なデータを抽出するとか中処理

    var query = 'INSERT INTO log (created_at, user_id, url, method, request_body, request_query, referer, session_id, user_agent) VALUES (NOW(), ?, ?, ?, ?, ?, ?, ?)';
    var connection = dbConnection.Connection();
    connection.query(query, [user_id, url, method, request_body, request_query, referer, session_id, userAgent], function (err, result) {
        next();
    });
};


あとは、ログインしたりする時にreq.bodyの中にはパスワードが平文で入っているので、それを削除する。forで回しているのは request_body = req.bodyみたいにすると参照渡しになってしまって、連想配列のpasswordをkeyにしたのをDELETEすると元のやつも消えてログインできないのでこういう形に。concatとか使うという話もあったけど、まぁ一旦この形でGOしました。後は、特定の記事とか会社とかそういうったよく出てくる大事な単位というのがサービスに存在しているのなら、そのidなり識別子を抽出してDBに突っ込んでおくというのも良いよね。

Android studioでgoogle mapsのView作ろうと思ったらapp:dexDebugなどとfailedしてしまう件

タイトルに情報量がありすぎて笑う。


Android studioAndroidアプリ開発の勉強を進めている途中で、google mapsと連携したActivityを作ろうとすると

Error:Execution failed for task ':app:dexDebug'.
> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/java'' finished with non-zero exit value 2

などと、エラーが出てしまいbuildが出来ない。

何かgradleとかいじっちゃったのかな、と他のプロジェクトをbuildしても問題なし。
新たにプロジェクトを作ってgoogle mapsを使わなければこのエラーは出ず、google maps activityを作成するとエラーが出る。


未解決。辛い。


色々調べてみると、compileする時に読み込むjarファイル内に同じクラスが存在しているとかでそこを回避する、とか書いてる(´・_・`)
google mapsを使おうとすると要らぬものまで読み込まれてエラーになるってこと?
まぁ、色々開発してたらあり得るとは思うんだけど、デフォルトのクラス作ってまず動作確認のためにビルド実行ポチーってやったら止まるとか、なにそれ、つらい。

ビルド通った。

build.gradleで

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"

    defaultConfig {
        applicationId "com.example.sugi511.mapsdemo"
        minSdkVersion 21
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'com.google.android.gms:play-services:9.2.0'
}

上記の compile 'com.android.support:appcompat-v7:24.0.0' というところをコメントアウトしたら通った。


f:id:sugi511:20160718012558p:plain


しかし、立ち上げるエミュレーターをミスったかまだ見れず。

ミスったと思われるものはGenyMotionで動かしてるので、一旦Android Studioから立ち上げることに。


f:id:sugi511:20160718013501p:plain


遅い、遅過ぎるし、現実問題geny motionは使いつづけるだろうということで、google playをどうにかする記事に習って環境を整えることに。

qiita.com

というか、Android studioのバージョンが2系が出てるのに、ずっと1.4系で動かしてたようだったので死にたい(´・_・`)

どれぐらい影響があるかも分からんが、まだ本開発には入ってないのに道具が古いってきついな(´・_・`)

新しいAndroid studioをインストール後

起動とNew project後の動きが速い気がするー₍⁽⁽(ી(*´ω`*)ʃ)₎₎⁾⁾

新しいバージョンで新たに作ったプロジェクトはビルドエラーでこけないから、まず悩みは解消。

次はgoogle playVMの上に乗っけるだけ!だけ!

これでうまくいったら、次にビルドした時にはmapが見えるようになっているはず・・・。



f:id:sugi511:20160718021805p:plain



こういうのめっちゃ待つよねー。仕方ないんだけどさ。

つづきはまたこんど。