みんなの「作ってみた」

【個人開発】中国人と交流できる掲示板サービスを作った話

2018/12/29

misumi3104
misumi3104
なんでもつくるよ

この記事は KMC Advent Calendar 2018 - Adventar の25日目の記事です。

こんにちはチャップリン( @misumi3104 )と申します。

アドベントカレンダー最終日を任されていたのに大変遅れて肩身が狭いです。

作ったもの

233ch.net

日本人と中国人が言語の壁を越えて交流できる掲示板サービス

注意してほしい事

一応完成しましたが満足するクオリティに至らなかったため積極的に宣伝せずにこの記事で供養します。

サービスのプログラムを切り抜いて貼り付けた箇所はコメントで雰囲気をつかんでください。

どんな感じのサービス?

233ch.net
2チャンネルのような掲示板サービスと最近深層学習を取り入れて性能が上がったGoogleの機械翻訳APIを組み合わせて日本人と中国人が言語の壁をこえて交流できるサービスを作りました。
screencapture-233ch-net-2018-12-20-19_17_33.png
図1 トップページ

日本語でも中国語でも書き込めます。

書き込んだ内容は即座に日本語と中国語に翻訳されます。

右上で中国語<->日本語を素早く切り替えられます
gif.gif
図2 言語の変更

私の開発環境

フロントエンド: Vue.js
IaaS: Python 2.7 on Google App Engine Standard Environment
OS: Ubuntu
IDE: Pycharm

なぜ作ったのか

  1. 既存のサービスがない

  2. 中国に興味があるが中国のコミュニティに入れるほどの語学力がない

  3. そのような人は両国に一定数存在する

失敗の原因

  • 不慣れなVue.jsで画面全てを描画した

私の使い方が悪いのかもしれませんがVue.jsで構文エラーを起こすとコンソールに何も残さずにページが真っ白になります。機能増やすとデバッグ時間が指数関数的に伸びて開発を切り上げざるを得ませんでした。初心者がVue.jsを使う場合は全体を一つのインスタンスで描画したりせずに局所的に複数のインスタンスを立てて描画するべきです。横着するとかえって手間が増えます。どうしても必要でなければ使わないほうがいいと思う。

  • CSSデザインの知識&技術がない

なんか全体としてもさっとしていて公開したくない気分です。本買って勉強するべきですね…

実装の工夫

失敗したものの身についたノウハウがいくつかあります。私の考えはプロのサービス開発者からみれば車輪の再発見かもしれませんが聞いてください。

単純サービス関数

アカウント管理などの複雑な処理を含むウェブサービスの開発にはプログラムが密結合に絡まり開発が困難になる懸念がありました。疎結合なコードに分割する工夫として複雑なWebサービスを単純な機能のWebサービスに分けて実装することにしました。

今回の掲示板サイトは以下の三つのサービスの組み合わせと考えられます。

  • アカウント処理だけのサービス

会員登録、ログイン、ログアウト、退会処理、設定変更だけができる

  • スレッドのランキング表示だけができるサービス

パラメータによって新着順や人気順でスレッドを並べるて表示するだけのサービス

  • 掲示板のスレッド処理だけのサービス

必要ならば書き込みを追加しスレッドのタイトルと時系列にならんだ書き込みの一覧を表示するサービス。

それぞれのサービスを関数に一対一対応させて実装することで見通しをよくしました。このような複雑なサービスを構成する単純なサービスを表した関数を単純サービス関数と呼ぶことにします。

  • アカウント処理だけのサービス statemain関数

  • スレッドのランキング表示だけのサービス stateranking関数

  • 掲示板のスレッド処理だけのサービス statechannel関数

それぞれの関数のインプットは辞書形式のパラメータでアウトプットは表示する内容を示すデータです。
スレッドのランキング表示だけを処理する関数は以下の通りになっています。

services.py
@statefunction
def stateranking(s):
    orderlist={"new":-unit.born,"old":+unit.born,"pop":-unit.rate}
    size=20
    page=int(s.page or 0)
    order=s.order if s.order in orderlist.keys() else orderlist.keys()[0]
    channellist = unit.query(unit.area == "channel").order(orderlist[order]).fetch(size,offset=size*page)
    return {
        "order":order,
        "page":page,
        "channellist":[i.format(musr=1) for i in channellist]
    }

具体的内容はともかくとして実行結果は分かりやすいです。
以下の呼び出しでは新着順のランキングを表示するのに必要な情報を入手しています。

console
>>stateranking(order="new")
>>
{
  "order":"new",
  "page":0,
  //掲示板のスレッドのこと
  "channellist":[
    //それぞれのスレッド
    {"namejp":"誰か話そう","namech":"聊聊一下吧","category":"chat","countview":13},
    {"namejp":"趣味","namech":"爱好","category":"study":countview:3}
    ...
  ]
}

単純サービス関数を使うとHTMLのテンプレート描画は以下のように見通し良く書けます。

access.py
        # ホームページにアクセスされた場合
        if path=="/":
            # home.jsはテンプレート
            # statemain,staterankingという二つの単純サービス関数で表示するデータを得る
            # データをテンプレートに流し込み結果を表示する
            s.write_temp("home.html",{
                "statemain": statemain(**s.params),
                "staterankingnew": stateranking(order="new"),
                "staterankingpop": stateranking(order="pop")
            })

会員登録などの処理も簡単に書けます

        # アカウント管理関係の処理はVue.jsから/mainに投げられる
        if path=="/main":
            # クエリパラメータを全部まるごと渡される
            r=statemain(**s.params)
            # ログイン状態ならばクッキーに書き込む
            s.setcookie("account",r["account"] and r["account"]["key"])
            # データをJSONで返す
            s.write_json(r)

各単純サービス関数ごとにテストコードを書けば効率的にデバッグできます。後から機能を拡張する場合も追加機能に対応する関数を定義しそれをURLに紐づければ終わるので整合性に頭を悩ませなくて済みます。疎結合は正義です。

サービスの設計

プログラムに限らず事前の設計は大事です。家を建ててから土台を固められないように正しい順序で決めていかないと破綻します。ウェブサービスの個人開発の場合は私は以下の順で決めることにしました。

  • サービスの機能を書き下す。
  • サービスを複数の単純なサービスに分割する
  • 単純サービス関数の名前、インプット、アウトプットを決める
  • 各関数とURLの対応関係を決める
  • 各関数を実装しフロントエンドを作る

まとめ

今回は残念な結果でしたがノウハウが貯まったので次はまだ楽に作れるでしょう。
今作りたいウェブサービスを上げておきます

  • このサービスのリメイク
  • 海外アニメRWBYのセリフの暗唱の進捗をアップロードし合うことでアニメ見ながら英語を学習するサービス
  • キャラの絵と自分の声を組み合わせてセリフ劇を作って公開できるサービス

以上ですありがとうございました。