SiG Staff Blog

やっていることをつらつらと

Travis-CIを試してみた

TDDに恋い焦がれて

プログラムを書いてる途中でも安心したいんです。

実務でテスト駆動で開発をしたいと思い、その環境をどんなものにしようかと思いJenkinsを使って試してみたのですが、 自由度は高くて良かったのですが、その代わりに色々と設定があり いずれは他の人にやって貰うためには少し敷居が高すぎだったので、

いまさら感はありますが、巷で話題のTravis-CIを触ってみました。 googleで調べると色々とやり方が書いてあったような、無いような気がしましたが、 とりあえず、シンプルな感じで動かしてみました。

実際にしたこと

  1. GithubTravis-CIのアカウントをとる。
  2. サービスを連携する。
  3. Laravelのプロジェクトを作る。
  4. yamlを作る。
  5. pushする。

これだけでした。 composerのコマンドを叩かないといけないところでちょっと躓きましたが、簡単です。 知ってればすぐ環境できますね。

www.slideshare.net

いずれは

  • developブランチにマージがあった場合は、テスト + 開発にデプロイ
  • masterブランチにマージがあった場合は、テスト + ステージングにデプロイ
  • masterブランチにタグ付けがあった場合はテスト + 本番にデプロイ

という仕組みにしていきたい。

しかし、問題が

会社がヌーラボさんのbacklogを使用しているので、Travis-CIが使えないという罠・・・。 色々とサービス連携するために、Githubに移行してもらいたいな。

別の物

phpstorm、upsource、youtrack、teamcityとJet Brains製品で固めたい欲求もあり。 teamcityをちょっと試してみようかと思います。

これらを使うと、phpstormだけでほとんど完結してしまうので、社内で広めたい。

スクラムガイド2016を読んでみました 2

実際に何するの?

前回は、大体の登場人物を把握したので、日々何をすべきかってのを纏めてみる。

スクラムを全然知らない人がスクラムガイドをよんでこうだろうなぁって思った内容なので、 間違いがあるかと思いますが、ご了承くださいませ。

まずプロジェクト始まった時にするところから

f:id:Bee_Flim:20170626222402j:plain

  1. PO(お客さんとプロジェクトリーダー)が必要な機能や仕様を洗い出して、何かに登録する。細かく。
  2. POがその工数を見積もる。その後、メンバーで工数を見積もる。
  3. ざっくりみんなでガント化する。
  4. チーム内で、毎日する事、手順、単語、成果物に対する考えとかを共有する。
  5. スクラムウォーターフォールの違いを説明する。

一発受注で「ハイ作って」契約だと、こんな感じでやるのが良いのかなと思いました(ので投稿してるときにやってみてます。)

簡単そうですが

ウォーターフォールとか、普通の学生 -> サラリーマンの流れの人の場合、

  • 指示を受けてしか作業を出来ない。
  • 指示を受けたことの作業しかしない。

の人が多いため、「メンバーで工数を見積もる」すらまともにできませんでした。最初。 なので、こんな感じにしました。

  1. 最初はこっちで見積もりとガントを作ってしまいます。
  2. そのあと、見てもらって大丈夫そうか返答をもらいます。
  3. 1時間毎に目標を立てて、50分間作業、10分振り返り(何で遅れたか or はやかったか)。
  4. 次に活かそうね!

後で出てくる「スプリント」を決める際の特訓にもなりますね。

あとは、指示待ちが多いので、「なんか詰まってない?」、「それをどうしたいの?」という方向で聞く。 意思を聞く。

多分、はじめは返事が返ってこないと思うし、向こうにお前が決めるもんだろうってイライラされると思うけど、聞く。 仕様については教えるけど、実装方法については自主性を付けてもらわないと困る、だからしかたない。

煙たがられてもいい

そのうち、自分が選択した方法で進めるという事に意味を感じてもらえると思う。

定例を決めて行こう

f:id:Bee_Flim:20170626222404j:plain

  1. スプリントという単位をチームで決める。(ソフト開発なら1週~2週?)
  2. そのスプリントでどこまでを実装するかをメンバーが決める(POは妥当性だけを確認すればいいかと)
  3. スプリントが終わったら、コミットできたこと、出来なかったことをみんなで出して、何でそうなったか、対策はあるか?とかを話し合う。
  4. 次のスプリントのコミットを考える。

簡単そうですが、これがまた思っているようにできない

その週にどこまでを実装できるかを決める。

困りポイント

  • 希望でしかない
  • 残業前提である

どうしてもガントを守ろうという流れになるので、阻止したい。 最初の見積もり時にこのスケジュールで定時でOKってしたはずなので、19時には帰る流れで出してもらう。

多分、これが現実的にはメンバーからこの条件で出してもらうの難しい(嫌がる)し、昔ながらの上長には怒られると思う。

なもんで、上長には遅れたらスケジュールを延ばせるてもらうとか、そういう手はずをプロジェクト始まる時点で整えておく必要があるかと思います。 自分だけで裏でこっそり球を持っててもいいです。延ばせるようにしておく。

もしくは、即座に後れをキャッチして報告する。今後の見通し込みで。今後このように遅れるので、調整をしてくださいと。

まぁ、頭の固い人相手だと、苦労しますが、出来ないものはできないし、そもそも後でおんなじような報告しないといけないんだし、先にしておいたほうが楽ですよね。多分・・・。

振り返り

困りまポイント

  • そもそもの、原因を追究しないで話が終わってしまう。
  • 一方的な報告だけになって意味が無い。
そもそもの、原因を追究しないで話が終わってしまう。

前者は、取りあえず「何に」かを追求するしかない。(何で?だとただの知識不足って事になってしまう)

  • その詰まりは、俺もしたよ。
  • おれ、そのライブラリ作ったけど・・・。まだpushはしてないけどな!

見ないな話が出たりするようになります。きっと。 この時も、その人が「遅れを責められている」みたいに感じるみたいです。

自分でやるっていった範囲が出来なかったので、そう思うのはしかたないですね。 次のスプリントの際に「自分でやるっていった範囲」が身の丈に合う様にセットしてもらうしかないです。それのせいで遅れが出るとしても。

他の人に、「進んでいたらキャッチアップして助けてあげて」と声をかけましょう。 まぁ、最初はいい顔はあまりしませんがw

一方的な報告だけになって意味が無い。

最初に、みんなにやりたい報告に仕方を言いましょう。 そして、意見を聞く時に個人あてで振ってみましょう。 たぶん、うまくいかないですが orz

振っても意見が出ない時に、俺はこう思うんだけど、どうです?って聞くといいかも。

これらの困りポイントが出る原因が

「リーダーから指示がきて、それをやる」 という、命令系統ができてるって認識があるから起きるんだと思います。

フラットにやるんだよ。って伝えるの難しい。

スクラムの運用の手順は

f:id:Bee_Flim:20170626222406j:plain

やる内容は簡単なんです。 その中身のクオリティをあげるのが難しいというか、 形骸化してしまっている内容を、しっかりとした目的をもってやるというのが大事。

その為には、

個々にチームの一人であるという認識を持ってもらう必要がある

と思いました。実践してみて。

「集まり」ではなく「チーム」。

次は、もう少し大きいスパンでの振り返りと、まとめとか

スクラムガイド2016を読んでみました

スクラムを基礎から学んでみたいそう思った。

なんでスクラムを?

  1. 今行っている会社では、SESや請負開発を主にしているのですが、 相手企業がアジャイル的な開発を進めている場合はそれっぽいことができるのですが、 こちらも、相手企業も体系的にスクラムなどを学んでいるわけではなく、それっぽいものをやっていだけなので、 結局のところ力業で進めてる。
  2. 相手企業に説明する力がないので、向こうの手法に準ずるしかない
  3. そもそも自社がウォーターフォール開発してる・・・(ツライ)

って状況が嫌だ!

という漠然とした理由とともに、、、

いつもの

という、単純に興味持っただけだったりもします。 いや、取り入れれるものならやってみたいので取り入れますよ。

lean-agile.fm

agileradio.github.io

でも、いきなり重たいのをやりたくないから、ガイドを読んでさらっと学んでみよう。

Scramに出てくる人

f:id:Bee_Flim:20170626222353j:plain

ざっと、4種類の人がでてきて、 それぞれを現実世界に照らし合わせると、

f:id:Bee_Flim:20170626222355j:plain

こんな感じ。 スクラムに詳しい人」なんていねーよ

ガイドを読んだうえで、こんな感じかなぁってのを上げてみますが、 間違っている部分があるかもしれません。 LTみたいな感じで暖かく見てください。

ステークホルダーの役割

  • お客様
  • 偉い
  • 成果物をチェックする
  • フィードバックをする

成果物チェックは受け入れテストを実施してもらうということで、よくあるけど フィードバックがほしいってことを言わないといけないですね。

スクラムマスター

  • 随所でアドバイスをくれる人
  • 目標値の妥当性とか、スクラム的かをアドバイス
  • 社外の偉い人にやってもらうのもあり

たぶん、この人が最初いないとスクラムをやっていくの難しい。

プロダクトオーナー

  • 成果物(プロダクト)の責任者
  • WBS(必要な全作業)を洗い出してチケット化
  • チームの作業が楽になる事は全部する
  • 雑用係・・・

たぶん、雑用する人。でも要。 開発とかは本当はしないのがいい立ち位置なんかなぁって思う。

チームメンバー

  • スプリントの目標を立てる
  • 実際の作業をする
  • プロダクトに対して仕様を満たす責任を持つ

開発者であり、仕様決定者という感じかな。

おさらい

f:id:Bee_Flim:20170626222400j:plain

プロジェクトでよくある、

設計者 -> 開発者 -> テスター

って流れではない

ってことをみんなで認識合わせがいりますね。

設計も開発もテストもチームメンバーの1人1人がおこなう

って所が、よくあるの開発現場と一番違う気がする。

具体的には何をするの?

スプリントとかデイリーミーティングとか振り返りとか、色々あるみたいですがそれは次回に。

AIはプログラマの仕事を奪うのか

AIはプログラマの仕事を奪うのか

ディープラーニングやら、自然言語処理やらAIやらニューラルネットワークとかが世間をにぎわして、 Alpha GOがトップ棋士勝利をしたりしてるなか、 将来、人がやる仕事が減るんじゃないかとおじさん連中が騒いている。

本当にそうなのか??

過去に機械が入り、これまでと全く違う手法が生まれた

過去にそんな業界があります。

それは、音楽業界(のリズム)

超絶な技巧で演奏される速いリズム。

でも、リズムマシンサンプラーなどの登場によって今までの技巧ではできない様なリズムや 人には到底演奏できないリズムが作られ、ドラマーのやってることは古いんじゃ?みたいな風潮が流れたとか流れていないとか。

これらの存在はもう一つ大きく変化をもたらしており、全く演奏できない、音楽の知識もない人でも感性だけでリズムを作ることができるようになって これまでの優等生なリズムではない、クールなちょっと悪めのリズムが作られるようになりました。

人では演奏できない様な複雑なリズムもつくられたりしていました。 人がドラムを叩かなくていいとか言われたりもしていたみたいです。

でも、その出来ないと言われたリズムを気合で習得し、 さらに機械では表現しにくい、割り切れないリズムや回り込んでいくリズムの修練度が高くなって、 機械を追い越しているような気がします。

少なくとも、機械のせいで仕事を失ったりしていません。 機械というライバルが出ても、同じことを続けていたのでそういう人は落ちて行ったのかなぁと。

そこそこ出来るは趣味で終わる

わけではないと思う。

これまでは、そこそこ出来る人でも仕事があって、食べていけるぐらいの報酬がもらえていたけど、 AIなりで自動化されることで、その報酬が下がって食べていけなくなるかも。 かといって、AIがその辺の人に弄れるわけではないので、「AIを使ってそこそこの物を作る」人になればいいんだと思う。

新しく出てきたのに負けないぐらいに育つか、 それを使っていくか、 または、それ以外の何かの手を打つか。

これって、別に今まで会社がやってきたことと変わらないんじゃないかなと思ってる。 コンピューターが出てきた時の様に、やり方が大きく変わるかもしれないけど。

PHPにDIは必要か という話を聞いて。

PHPの現場 にて

最近PodCastにはまって、テック系の物をいくつか聞いてるんです。 フロント系が多いイメージのPodCastですが、PHPの話をしてくれる珍しいチャンネルがあって、 放送されたら必ず聞いてるんですが、そこでDIコンテナってPHPにいる?という話がありました。

Zendで更新が停滞しているオレオレフレームワーク作っていて、ふむふむそうだよねって思ったのでちょっと紹介。

php-genba.shin1x1.com

podcsstの概略

  • 便利は便利だから、DIの使いどころを間違わないようにしよう。
  • 今後、DIコンテナの有無でフレームワークの選択を分ける感じになるんでは。

うろ覚えなんだけどこんな感じで、結論的には、

DIコンテナは便利なんだけど、ちょっとPHPを使うにあたって大げさすぎるシーンが結構ある。

こんな感じ?だったと思います。

個人的な感想

DIは便利なのであったほうが良いかと思う。 ただ、あったほうが良いシチュエーションっていうのが結構おっきめのプロジェクトであることが多いかと思ってて、 そういうプロジェクトの時はF/W選んでポチポチ準備していくのはありかもしれないが、 そうじゃない、数か月だけ稼働するとかの場合はDIみたいな仕組みは無くて、突貫で作れればいいやん。

PHPの現場では、「それぞれのシチュエーションに合わせてF/Wを選ぶ感じになるじゃない?」という事だったけど、 DIコンテナありのF/Wにオレオレカスタマイズを入れて、

  • 短期集中の時は細かい設定せずに身を任せる。(どうせそんなに大量の機能が無いだろうし)
  • 長いプロジェクトの時は、設定とかテスト設定とかゴリゴリやる。

って感じにどっちでも転がれるようにF/Wの設定済ませて有る物を準備すればいいんかなぁと思った。

ちょこちょこ作ってるオレオレフレームワーク、Zendで作っているけど、Laravelをしてみたいから移行してみようかな。

兎にも角にも、次の収録が放送されるのが楽しみ。

指示通りSkeletonを使ってサンプルサイト作ったけど、なんかワーニング出るやん!

Zend Framework2で急にワーニングがでた!何もしてないのになんで!?

指示通り作ったのにワーニングがでてる!

Zend Framework2に書いてある通り、 Skeltonを入れて、 サービスの登録とかを諸々したら画面が出る(出てた)んだけど

ある日、

Deprecated: You are retrieving the service locator from within the class Application\Controller\IndexController. Please be aware that ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections.

って文字が画面の上に出てる。 前までちゃんと動いていたのになんで???

Zendは3.0に向けて動いてるらしい

3.0からはZendもDIっぽくやっていくよ!って事らしく。

ServiceとかでServiceLocatorを使う時に、$this->serviceLocator の様に呼びださずに Module.phpのgetServiceConfigメソッドでServiceのクラス変数に値を入れるようにしなさい。 って事らしい。

$this->serviceLocatorみたいに呼びだせないようにするからね!って事だそうで。

いきなりDeprecated出るようなアップデートしないでYO

で、AwareInterfaceってなんぞ?

ControllerとかにLoggerを持っているか、ServiceManagerを表現するインターフェースのようで、

LoggerAwareInterfaceを持ってたらsetLogger()メソッドを持たすことを強要できるので、 Controllerクラスを作成するタイミングで、クラス変数とかに$loggerとかにセットしてあげればloggerが使えるという感じです。

ServiceManagerAwareInterfaceだったら、setServiceManager()メソッドをつけれるので、Controllerにサービスマネージャをセットできます。

という事らしい。

で、やってみた。

各コントローラのクラスにLoggerAwareInterface, ServiceManagerAwareInterfaceをimplementsする。

abstract class BaseController extends AbstractActionController implements LoggerAwareInterface, ServiceManagerAwareInterface
{

    /** 改行をBRに変更する */
    public $nl2br;

    /** ロガー(monolog) */
    protected $log;

    /** サービスマネージャ */
    protected $sm;

    /**
     * loggerの設定。initializerで設定
     */
    public function setLogger(Logger $log) {
        $this->log = $log;
    }

    /**
     * サービスマネージャの設定。initializerで設定
     */
    public function setServiceManager(ServiceManager $serviceManager) {
        $this->sm = $serviceManager;
    }
    /*
     * 諸々の処理
     */

で、初期設定をするためのクラスを設定する。

/**
 * コントローラーの初期設定を行う
 */
class ControllerInitializer implements InitializerInterface
{
    public function initialize($instance, ServiceLocatorInterface $serviceLocator)
    {
        //loggerの設定
        if ($instance instanceof LoggerAwareInterface){
            $instance->setlogger(\Application\Common\Log::createBasicLogger(Setting::APPLICATION_NAME));
        }

        //service managerの設定
        if ($instance instanceof ServiceManagerAwareInterface){
            $instance->setServiceManager($serviceLocator->getServiceLocator());
        }
    }
}

DIコンテナもどきなので、Module.php経由でコントローラを呼びだす設定の所に、 イニシャライズとして、さっき作ったクラスを指定する。

    /*
     * コントローラーの設定をする
     */
    public function getControllerConfig() {
        return array(
            'invokables' => array(
                'Application\Controller\BaseController' => 'Application\Controller\BaseController',
            ),
            'initializers' => array(
                'Application\Initializer\Controller\ControllerInitializer',
            ),
        );
    }

で、便利なん?

LoggerとかSMとかだとあんまり嬉しくないけど、ファイルに保存するクラスなどを

  • ローカル開発なら、ローカルに保存するクラス。
  • STGならS3に保存するクラス

などに差し替える事ができるので、便利ですね。

slack + hubot + Backlog Apiでプロジェクトの予定と実績の修正をとる

色々あって便利ですよね、Backlog

Backlogいいね!

これまで、クラウドサービスの使用禁止という世間の流れから逆流した開発をさせられてて、

Redmineでタスク管理をしていたのですが、ようやくBacklogの使用許可がおりて使っております。

ticketも使えるし、wikiもあるし、Gitもあるしいいよね。社外でも見れるし。

Backlogよくないね!

Redmineと違って、サブプロジェクト作れない、チケットが親子までしか作れない、Gitのレビューコメントがちょいと見にくいとかあるんですね。

APIが微妙に使いにくいとか、webhookがちょっと微妙とか・・・。

でも、これらは無くても全然プロジェクト進行できるしいいんですよ。

困るのはサマリー系が全然ない事。予定・実績とかの管理が全然できないんです。

どうしたもんか・・・。

なければ作ればいいって偉い人が言ってるよ

Backlog API課題の情報を取得するのがあるやん。

プロジェクトの課題を全部取ってきて、自分でサマリーしてSlackに通知すればいいんじゃ?

ってことで、herokuにslack連携を済ませてあるhubot がいるので彼の仕事を増やしてあげる事にしました。

欲しいのはソースですよね・・・。

動かし方

必要な物は

  • Backlogのトーク
  • スペースID
  • プロジェクトの情報

です。これらを

  • api_key
  • space_id
  • project_list

にいい感じにセットしてあげてください。

で、botにメンションを付けて

@botName give me 【呼出用のプロジェクト名】

ってすれば動いてくれます。リクエスト投げまくるので結果が返ってくるまで少し時間かかります。

ってことで、ソース

ソース

少し長いです。

  robot.respond /give me[ ]?(.*)/i, (msg) ->

    #Backlogのトークンを設定する。個人設定から取得する
    api_key = "【トークンの設定】"

    #BacklogのスペースIDを設定する。https://[ここの部分].backlog.jp
    space_id = "【スペースIDの設定】"

    #プロジェクトのリストを取得する
    project_list = 
      "【呼出用のプロジェクト名】":
        name: "【画面表示用のプロジェクト名】"
        id: "【backlogのprojectのID】"
        start: 1
        end: 300

    target_project = msg.match[1]
    
    if not (target_project? and target_project of project_list)
      project_names = ""
      for key of project_list
        project_names += key + ", "
      msg.send "どのプロジェクトなの? -> #{project_names}"
      return -1
    
    target_data = project_list[target_project]
    target_name = target_data["name"]
    target_id = target_data["id"]
    target_start = target_data["start"]
    target_end = target_data["end"]

    if not (target_name? and target_id? and target_start? and target_end? and isFinite(target_start) and isFinite(target_end) and target_start < target_end)
      msg.send "プロジェクトの設定値を確認してください。 -> name: #{target_name}, id: #{target_id}, start: #{target_start}, end: #{target_end}"
      return -1

    #小数点以下を切り上げするためのfunction
    floatFormat = ( number, n ) ->
      _pow = Math.pow( 10 , n )
      Math.round( number * _pow ) / _pow

    #Backlog apiを使って課題の集計をするfunction
    # @param bl_project_id プロジェクトID (プロジェクト作成した時に指定する値。課題の前に付くあれ)
    # @param start 対象にするチケットの開始位置
    # @param end 対象にするチケットの終了位置
    call_backlog = (bl_project_id, start, end) ->
      #ループのサイズ(ループをStopさせる用)
      loop_sise = end
      #ループの開始位置
      loop_start = start
      #ループの終了位置
      loop_end = loop_start + loop_sise - 1
      #課題データ
      actual = {};
      #課題データを貯めるリスト
      actual_list = [];
      #作業位置
      finish_cnt = 0

      #プロジェクトIDを取得する
      back_log_url_project = "https://#{space_id}.backlog.jp/api/v2/projects/#{bl_project_id}?apiKey=#{api_key}"

      request = robot.http(back_log_url_project)
                     .get()
      request (err0, res0, body0) ->
        json0 = JSON.parse body0
        project_id = json0["id"]
        
        #課題の数を取得する。
        #課題の取得が非同期なのでうまく止まるようにチケットの最大値を取得する。
        #Promiseを使えればきっとうまくできるはずなんですが。
        back_log_url_count = "https://#{space_id}.backlog.jp/api/v2/issues/count?apiKey=#{api_key}&projectId[]=#{project_id}"

        request = robot.http(back_log_url_count)
                      .get()
        request (err1, res1, body1) ->
          json1 = JSON.parse body1
          
          #設定されている値がチケットの数よりも多いと、複数回サマリー結果が出てしまうので
          #チケットの数までループするように変更する
          ticket_max_size = json1["count"]
          if loop_end > ticket_max_size
            loop_end = ticket_max_size
            loop_sise = loop_end - loop_start + 1

          for i in [loop_start..loop_end]
            
            #課題を取得
            back_log_url = "https://#{space_id}.backlog.jp/api/v2/issues/#{bl_project_id}-#{i}?apiKey=#{api_key}"

            request = robot.http(back_log_url)
                          .get()
            request (err, res, body) ->

              #上手くパースされないので特殊文字を消す
              body_new = body.replace(/\\n/g, "")
                          .replace(/\\'/g, "'")
                          .replace(/\\"/g, '')
                          .replace(/\\&/g, "")
                          .replace(/\\r/g, "")
                          .replace(/\\t/g, "")
                          .replace(/\\b/g, "")
                          .replace(/\\f/g, "")
              json = JSON.parse body_new
              
              
              #エラーチェック
              if ("errors" of json)
                finish_cnt = 9999
                return 0
              else
                #値が入ってこない時があるので、デフォルト設定
                eh = if json["estimatedHours"]? then json["estimatedHours"] else 0
                ah = if json["actualHours"]? then json["actualHours"] else 0
                assignee = json["assignee"]

                name = if ( assignee? && "name" of assignee) then json["assignee"]["name"] else ".未設定 "

                actual = 
                  name: name
                  estimatedHours: eh
                  actualHours: ah
                  status_unopend: 0
                  status_opened: 0
                  status_treatmentted: 0
                  status_closed: 0

                #チケットの状態を保持する
                if json["status"]["id"] == 1
                  actual["status_unopend"] = 1
                else if json["status"]["id"] == 2
                  actual["status_opened"] = 1
                else if json["status"]["id"] == 3
                  actual["status_treatmentted"] = 1
                else if json["status"]["id"] == 4
                  actual["status_closed"] = 1

                #後でサマリーをしないと値が消えてしまうので
                actual_list.push(actual);

              #処理件数をincrement
              finish_cnt += 1

              #処置が全部終わったら出力する
              #非同期処理の中で1つかこの処理が走らない
              if finish_cnt >= loop_sise

                #ユーザー単位でサマリーする
                output = {}
                for actual, idx in actual_list
                  key_name = actual["name"]
                  if key_name of output
                    data = output[key_name]
                    data["estimatedHours"] += actual["estimatedHours"]
                    data["actualHours"] += actual["actualHours"]
                    data["status_unopend"] += actual["status_unopend"]
                    data["status_opened"] += actual["status_opened"]
                    data["status_treatmentted"] += actual["status_treatmentted"]
                    data["status_closed"] += actual["status_closed"]
                    output[key_name] = data

                  else
                    output[key_name] = 
                      estimatedHours: actual["estimatedHours"]
                      actualHours: actual["actualHours"]
                      status_unopend: actual["status_unopend"]
                      status_opened: actual["status_opened"]
                      status_treatmentted: actual["status_treatmentted"]
                      status_closed: actual["status_closed"]
                
                str = " *-------- #{target_name} プロジェクト実績 --------* \n"

                #出力をする
                for k,v of output
                  str += "   * #{k} *: #{v["actualHours"]}/#{v["estimatedHours"]} H( #{floatFormat(v["actualHours"] / 8, 2) } / #{floatFormat(v["estimatedHours"] / 8, 2)} 日) [ 未:#{v["status_unopend"]}, 中:#{v["status_opened"]}, 済:#{v["status_treatmentted"]}, 完:#{v["status_closed"]} ] \n"
                
                msg.send str

                return 0

    #指定したプロジェクトのサマリーを出力
    call_backlog(target_id, target_start, target_end)

    #ヘッダとしてだす
    msg.send "ちょっと待ってね♪"

出力内容

*-------- XXXX プロジェクト実績 --------*
>   ユーザーA: 0/30 H( 0 / 5 日) [ 未:10, 中:0, 済:1, 完:0 ] 
>   未設定 : 0/1074 H( 0 / 134.25 日) [ 未:109, 中:0, 済:0, 完:0 ] 
>   ユーザーB: 0/0 H( 0 / 0 日) [ 未:0, 中:1, 済:0, 完:0 ]

もうちょっと上手くできるんでは?

Promiseとか使えばコールバックのネストが無くなるんじゃ?

coffeeが1.6だったのでPromiseが使えなかったんです。 どうすれば使えるようになるんですかね・・・。

未設定のユーザーって一番上か下に出たほうが良いんじゃ

作った後に気が付いたんですが、ソートするのがめんどくさくて。。。 最後の出力の所でごにょごにょして対応しようと思ってます。

作ってみて

そいういえば、非同期で送信するんだった。ってので思ったより嵌りました。(Promise使えなかったし)

ちょっと遅いし、Backlogにちょっとした負荷がかかってしまうのが気になるんですが、統計が取れるようになったのは便利ですね~