2016/01/26


Go 言語の開発者 Russ Cox 氏が2010年に「変数名の長さ」について書いた文章です。

research!rsc: Names

David Andersen (February 4, 2010 2:37 PM) A few comments: Rob: You wrote, "I could see if j is just ...

http://research.swtch.com/names

全てのプログラマは変数の名前付けに関する哲学を持っている。これは私の哲学である:

名前の長さはその情報の中身を超えるべきではない。 ローカル変数の場合、名前 i は index や idx といった情報を限りなく即決に伝える。 同様に i と j は、i1 や i2 (index1 や index2 はさらに良くない) よりもインデックスを名付ける為の良いペアである。なぜならばそれらはプログラムを斜め読みする時に個別に伝えやすいからである。 グローバルな名前は相対的により多くの情報を伝えなくてはならない。なぜならばそれらは色んなコンテキストに現れるからである。 短かったとしても、この正確な名前は acquire や take_ownership の様な長ったらしい物よりもより多くを伝える事が出来る。 全てを名前で伝える。

何年か前に私はこのメトリックに移行したが、あまりにも多くJavaコード を見てきてしまったせいで最近この言い回しを実体験する事になったのかもしれない。

Posted at by



2015/12/15


linux - .bashrcでexportしたPATHが/procにあるプロセスファイルの環境変数(PATH)と一致しないように見える - スタック・オーバーフロー

Ubunt 15.04 を使っています。 どういう際に利用するかは措いておくとして、 /proc にある各プロセスIDの名前がついたディレクトリにある、 environ というファイルを、プログラミン...

http://ja.stackoverflow.com/q/19984/440

回答の中で答えられている通り、/proc/$$/environ (/proc/self/environ)は、プログラム起動時の環境変数だけが表示される。プロセスが起動中に設定した環境変数は表示されない。

pid1

なんとかして取れないかなーと思って以下のスクリプトを書いた。

#!/bin/bash

if [ "x$2" == "x" ]; then
  GREP=cat
else
  GREP="grep ^$2="
fi

gdb -q --silent -p $1 <<EOF | grep XXXXXXX | cut -c9- | $GREP
set \$i=0
while (1)
  set \$r=*((char**)environ+\$i)
  if (\$r == 0)
    loop_break
  end
  printf "\\nXXXXXXX %s\\n", \$r
  set \$i++
end
EOF

これにプロセスIDを指定して起動すると

pid2

動的に設定された環境変数の値が取れる(第二引数に環境変数名を指定してもok)。仕組みはコードを見ると分かるが、environ のポインタをずらしながら値を表示する gdb スクリプトを実行してる。

ただしこの方法は、あくまで setenv(3) を使って environ を更新するプログラムに限られる。例えば bash のあるバージョンでは environ を更新しないらしく、このスクリプトを持っても環境変数が取れなかった。言語処理系の中には環境変数は自プロセスには反映せず子プロセスの起動時のみ使用する物もあるので、そういった場合には使用出来ない。

Posted at by



2015/12/12


golang の defer は後処理のキューの登録です。コードを見ていないので分かりませんが、おそらくこういうコードを書いたのだと推測します。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        f := Create(i)
        defer Delete(f)
    }
    fmt.Println("終了")
}

この処理、実際には作成者の意図に反して以下の様に動作します。

開始
1 を作成
2 を作成
3 を作成
4 を作成
5 を作成
6 を作成
7 を作成
8 を作成
9 を作成
10 を作成
終了
10 を削除
9 を削除
8 を削除
7 を削除
6 を削除
5 を削除
4 を削除
3 を削除
2 を削除
1 を削除

つまり後処理を LIFO に登録し、関数スコープを抜けたタイミングで最後に登録したキューから取り出して実行します。ですので大量のループを実行するとキューが爆発します。さらに defer はその瞬間の変数をキャプチャします。

package main

import (
    "os"
)

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer f.Close() // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer f.Close() // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer f.Close() // test3.dat の Close()
}

func main() {
    doSomething()
}

つまり test1.dat の f も test2.dat の f も test3.dat の f もキューに乗っかります。それを確認する為にこのコードを以下の様に修正します。

package main

import (
    "fmt"
    "os"
)

func closeFile(f *os.File) {
    fmt.Printf("%v を Close() します\n", f.Name())
    f.Close()
}

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test3.dat の Close()
}

func main() {
    doSomething()
}

実行すると以下の様に出力されます(ファイルは存在しているものとします)。

test3.dat を Close() します
test2.dat を Close() します
test1.dat を Close() します

ですのでループの中で defer を使ってはいけません。ただしループの中で処理するステートメントが多く、defer を使って簡素化したい場合は、以下の様に関数スコープを作ってあげる必要があります。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        func() {
            f := Create(i)
            defer Delete(f)
            // ... 色んな処理
        }()
    }
    fmt.Println("終了")
}

もちろんこの場合、途中で return しても大域脱出にならないので注意が必要です。

Posted at by