技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

超久々に Python 弄ってた

AI 関連が実質 Python 一択で、数学系ライブラリもその表示も Python だとかなり揃ってたので、久々にやってみようとしてどハマりした記録。
何をしようとしたかというと、下記を Python で実装すればいいかなーとかとか漠然と考えてどハマりした。

プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問

プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問

どこにはまったかというと問題2。

問題内容は書籍を買うか、下記を見て欲しい。

white-azalea.hatenablog.jp

  1. Python だし、eval あるよねー使い方調べるついでに使って見るかー」
    → 9009 に * 突っ込むとしても 90*09 とかになるとダウンする。09 ってなんぞやー
  2. 「諦めて、* が必要な箇所で文字列区切って掛け算するか」
    → 変数タイポで 1h どハマり(実行時評価だから実行するまでわからないので単純なミスが見つからない)
  3. 「計算できたし正解を比較するか」
    → 久々にやってて問題文間違って覚えてて、「計算の前後で出た四桁数字が順不同で含まれてることじゃね?」と勘違いして実装。
    → 片方の文字列を1時づつ取り出して、もう片方に含まれるか?という判定をするが、122 - 124 でも true になる。
    → なら逆も比較したらええやんと思いきや、122 - 12 で結局OK判定になってしまう。
    → なので、片方の文字列を1時づつ取り出して、もう片方から1字づつ削除(immutable じゃなくて背中が痒く…)してみるも、 Java の String.replace 同様の replace メソッドを見つけたが、第3引数に指定がないと replaceAll 同等動作とか知らずにハマる。
    → 突破したら突破したで仕様違いに気づいて脱力…回文かよ…

で、色々やった挙句、こんな単純な問題に 3 時間も悩まされたという。

途中途中で Syntax error に悩まされたのもでかい。
TextMate2 を使っているが、やっぱ IDE なんかでリアルタイムに syntax チェックしてないとわからん。
加えて言えば、実行字型の為に、動かしてから死んでデバッグの後戻りがひどかった…

慣れの問題なのだろうか、例えば代入時に変数名が一時違った位でも、「新しい変数ができた」と認識されて平然と動いてしまう為、何が悪いか超追いかけづらかった…。

結局最後は全部関数にした…もう…関数型で…イイヨ。
たとえ…Python に… tail call recursive が実装されてなかったとしても…!

positions = [
    [1], [2], [3],
    [2, 1], [3, 2], [3, 1],
    [3, 2, 1]
]

def split(str, pos, stack):
    if len(pos) == 0:
        stack.append(str)
        return stack
    else:
        i = pos[0]
        stack.append(str[i:])
        return split(str[:i], pos[1:], stack)

from functools import reduce

def multiple(list):
    return reduce(lambda a,b: a * b, list)

def is_kaibun(left, right):
    return left == right[::-1]

for cur in range(1000, 9999):
    strCur = str(cur)
    for pos in positions:
        splitted = split(strCur, pos, [])
        result   = multiple(map(lambda v: int(v), splitted))
        if is_kaibun(strCur, str(result)):
            print(str(splitted) + " = " + str(result))
            print("success: " + strCur)

Java8 の Stream が物足りない人たちへ

www.vavr.io

言ってみれば、Scala 並の Collection を Java で提供するライブラリ。
何がいいって、Tuple とか Either 型もあるから、クソッタレな null や throw とおさらばできる。

その昔 javaslang (java.lang があるなら俗語があってもいいよね)と名乗ってた。
個人的にはこっちのセンスが好きだったんだけど…物言いでもはいりましたかね。

まぁケースバイケースで。

JUnit をもう少し管理者にみやすくしてみた

今日紹介するのはこれ。
Scala でザクッと作ってみた。

github.com

制作時間は調べ物 6h 、実装 4h か…
まだまだ精進が足りない。

こいつは先日書いた、JavaDoc を XML で吐き出すメモ - 謎言語使いの徒然 と、JUnit の結果をマージして出力するツールだ。
サンプルこんな感じ。

$ java -jar ut_converter.jar javadoc/javadoc.xml ut > result.html
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

で出てくるのがこんな感じ。

f:id:white-azalea:20170611130253p:plain

JUnit は Spec じゃ無いから、表現力が低い。
そのため、メソッド名は目安になるけど、それ単品ではテストレポートとして出すのが難しい。

そこで、Javadoc で細かくテスト内容書いておいたら、まとめて表示してくれるツールがどっかで要るなと。
2週間位前にリポジトリは作ってたけど、やっぱ平日に手を出す余裕ないね(汗

引数パースライブラリの SCOPT(3.6.0) 使ってみた

紹介するのはこれ

github.com

依存はこれだけ

libraryDependencies ++= Seq(
  "com.github.scopt" %% "scopt" % "3.6.0"
)

で、引数を格納するクラスをこんな風に用意して

import java.io.File

case class Config(javaDocXml: File, junitResultDir: File)

パース設定とかを用意する。

import java.io.File
import scopt.OptionParser

object ArgParser {

  val parser = new OptionParser[Config]("UTXmlConverter") {
    head("ut_converter", "1.0-SNAPSHOT")

    arg[File]("[JavaDoc XML path]")
      .required()
      .text("Path for XML file that using MarkusBernhardt/xml-doclet.")
      .action((f, c) => c.copy(javaDocXml = f))
      .validate(v => {
        if (!v.isFile) Left("Not a file.")
        else if (!v.getAbsolutePath.endsWith(".xml")) Left("Not a XML file.")
        else Right()
      })

    arg[File]("[UT XML dir path]")
      .required()
      .text("Path for unit test results directory path.")
      .action((f, c) => c.copy(junitResultDir = f))
      .validate(v => if (v.isDirectory) Right() else Left("Not directory."))

    help("help").text("print this usage text.")

    note(
      "This program requires XMLs that generated by MarkusBernhardt/xml-doclet (var 1.0.5)\n" +
        " and Junit test results XML files."
    )
  }

  def parse(args: Array[String]): Option[Config] = {
    parser
      .parse(args, Config(new File("docs.xml"), new File("ut_results")))
  }
}

そして、引数指定しないで実行すると…

Error: Missing argument [JavaDoc XML path]
Error: Missing argument [UT XML dir path]
Try --help for more information.

おーいいね。
help 読んでみても…

$ java -jar ut_converter.jar --help
ut_converter 1.0-SNAPSHOT
Usage: UTXmlConverter [options] [JavaDoc XML path] [UT XML dir path]

  [JavaDoc XML path]  Path for XML file that using MarkusBernhardt/xml-doclet.
  [UT XML dir path]   Path for unit test results directory path.
  --help              print this usage text.
This program requires XMLs that generated by MarkusBernhardt/xml-doclet (var 1.0.5)
 and Junit test results XML files.

オプション引数や、引数バリデーションも書けるし、なかなかいいですね。

sbt を jar にしてみた

つっても何の事は無い。

github.com

これ突っ込んだだけ。

project/plugins.sbt に下記を追加して

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.4")

build.sbt でざっくり指定するだけ。

name := "Example"

version := "1.0-SNAPSHOT"

scalaVersion := "2.11.8"

mainClass in assembly := Some("net.white_azalea.Application")

assemblyJarName in assembly := "example.jar"

これして sbt assembly するだけで、target/scala-2.11/example.jar が出来上がる。
以上。

JavaDoc を XML で吐き出すメモ

やりたい事が何かと言うと、外部プログラムにおいて、Java クラスのリスト取得と、そのドキュメントを引き抜きたいと考えた。

しかしながら、単純に javadoc のコマンドだけ実行すると、HTML ファイルが出てくる。
これは JavaDoc コマンドの仕様であり、基本動作となっていて、中間ファイルなども出てきていないようだ。

要するに javadoc コマンドの中でパースやら構築やら全部やってしまっていて、その間にあるだろう Javaクラス構成とそのドキュメント構造を引っこ抜く事がこのままだとできない。
でも当たり前だが、同じことをしたい人なんて絶対いるだろうと…むしろ居ない訳がなかろうと思ったのだ。

そして散々悩んだところ、どうも JavaDoc には Doclet なる機構が存在するらしい。
この Doclet とは、JavaDoc で読み取ったデータを出力する際のデータ整形に使われるようだ。

つまりこいつに XML 生成の Doclet 食わせてやれば、XML が吐き出されると思われた。
で、探して見たところ案の定。

github.com

そりゃそーだよなと、むしろない訳ないよなと。

って事でやってみた。

続きを読む

くたばれカーゴ・カルト・プログラミング

何?

Wikipedia によると。

実際の目的には役に立たないコードやプログラム構造を儀式的に含めておくプログラミングのスタイルである。カーゴ・カルト・プログラミングは主に、プログラマが解決しようとしているバグか解決策のいずれかかまたは両方を理解していない場合に見受けられる。 他にも、スキルの低い、ないし新人プログラマ(または当面の問題を経験したことのないプログラマ)が、そのコードが何をしているか理解が足りないまま、またはもしかしたら新しい場所にも必要なのではないかと、別の場所から関係ない部分も含めてコードをコピーしてしまうことで発生する可能性がある。

ひいてはコピペ駆動開発

どうして

不要なコードは、ロジック的に余計な処理であるがゆえにバグを生み出す温床(しかも実装者がわかってない事が多く、余計にバグりやすい)であったり、本来行うべき処理を読み取る中でノイズ(雑音)となって、レビュー時間、もしくは保守時の工数を浪費してしまう。
性能(無駄な処理)、安全性(余計な処理によるバグ発生)、保守性(可読性の低下)全てに悪影響をもたらす忌むべきもの。

どうする?

大抵は書いてあることの意味を理解していない為に起こる。
これには大きく分けて二つのパターン、もしくは両方の場合がある。

  • コピペ元がそもそも煩雑で、追いかけ切れない
    • ボーイスカウトの原則を守られず、コードが秘伝のタレになっている
    • 色々検証してて、動いたからいいやとリファクタをサボった
    • 可読性に無頓着でともかく手続きを並べまくってそのまま
  • 実装者が元のコードを読む気がない
    • やる気が無い
    • 読んでる時間的余裕がない

時間的余裕がない場合を除き、コミット前に書いた部分を最初から見直すのが一番。
また、修正の場合でも関連箇所はもっと共通化分けできないかとか考えるのが良い(DRY原則ボーイスカウトの原則)。

「1行1行注視して読める」では足りない。
メソッド化と分離を繰り返すと、次第にメソッドの流れは単純な複数のメソッドコールになっていく。

結果として、「流し読みできる」レベルで抽象化しているのが望ましい。

蛇足

因みにレビュアーの観点でこれをやるプログラマのもっともあるある Top3 は

  1. 可読性に無頓着でともかく手続きを並べまくってそのまま
    可読性の重要性を理解していない。
    知らないだけのパターンが多く、丁寧に説明すれば大体わかってくれる。
    それでダメなら、スパゲティなコードの保守プロジェクトに突っ込んで、次に原理主義的に綺麗なコード(オープンソース)のカスタムを経験させるに限る。
  2. コードが秘伝のタレ
    特にプロマネがせっつくだけで品質に興味がない場合で発生しやすい。
    この場合、現場が整理したいって言っても大抵工数を理由に拒否されるか、「動いてるのに手を加えるなんてありえない」と拒否される。
    この場合は、バグ数を理由に、レビューの実施(にかこつけてリファクタしてしまう)。
  3. やる気が無い
    このタイプは大体2タイプ。どっちも「こんなに戻って仕事進まないなら覚えた方がマシ」と思わせたら勝ち。
    • もらう金一緒なのに苦労とかしたくない派
      → 超絶レビューバックで矯正。
    • そんなのしなくてもやってこれた派
      → 汚いコードに劣等感持つまで、リファクタのやり方を指摘。