新卒2年目のエンジニアの自分がやっている、心掛けていること

初めに

こんばんは。初めましての方は初めましてかもしれません。

少し新年明けて思うところがあったので書いてみます。
ネガティブな話はできるだけ避けて。

まず、自分の素性を軽く説明しておきます。

新卒で小さいSIerに就職しました。
今は現場に常駐して作業を行っています。チームリーダーみたいなことをやっています。
仕事では主にJavaやったりJavaScriptやったり、してます。
趣味ではなんとなく触りたいものから、仕事の調査みたいなことをやってます。

一つ前提として

この記事には根拠も全然ありません。その辺は妄想で補ってください。

この記事で書いていることは特に何の意味も役に立つこともないかもしれません。
それでも自分の思いとして、ここに残しておきます。
ポエムです。技術の話はたぶんありません。

1. 声をかけるときには名前を呼ぶ。

「〇〇さん、今、少しお時間よろしいですか?」

これは僕自身、名前が覚えられない・思い出せないことが前提にあります。

また、人の名前を呼ばれると承認欲求が満たされるみたいな話を見た覚えがあります。
なので、気持ちよく会話を始めるために、声を掛けるときには名前を呼んでいます。

気持ちよく仕事したいですね。

2. 相手の顔・目を見て話す。

相手の話を聞いてますよ、という意思表示ですね。

人間はボディランゲージや第一印象が与える影響は大きいという話がありまして
この辺は就活本とかにも載っていたかと思います。
個人的にはメラビアンの法則みたいな話を学生の時に聞いた覚えがあります。
相槌とかも打つといいかもしれませんね。

また、顔を見て話す理由としてもう一つありまして
これは、自分が話している内容が
相手に伝わっているかどうかを確認しています。

気をつけたいところなのですが、なかなか難しいです。

3. 声を掛けた際に簡単に要件を話す

「(以前お話をさせていただいた|先ほどメールさせていただいた)
 〇〇の××に関してお話させていただきたいのですが」

この時点で相手が「あ、はい。なるほど。」と言えば
要件を話せばよいと思いますが
たまに顔にはてなマークが浮かんでる時があります。

これに関しては次の話とまとめて話をします。

4. 話が伝わっていない時は一旦話を止めて、確認して、もう少し詳しく説明する

「(ほげほげほげ)・・・えーっと、なんか分からないことあります?」

話が伝わらないことには先に進めません。
一旦、相手に喋ってもらって、コンテキストの共有に努めます。
また、無駄に刺激してしまうので話を聞いてもらえないことがあります。
顔がうんざりしてたら、一旦止めて確認しています。

5. 曖昧なところはすぐに確認する。

要望と対応内容が噛み合ってない時がたまにあったりします。
そういった場合に、曖昧な要望と曖昧な期待する対応内容が与えられることがあります。
すぐに確認しています。(メールでも電話でもいい。)

6. 笑顔で話す

これは難しいです。職場には嫌いな人間は多少なりともいます。
でも気持ちよく仕事したいので笑顔で接します。

7. 間違いを認める。

会議中に議論している時に、自分が間違ったことを言っている時があります。
というか、設計上の問題に気づく時があります。
こういうときは正直恥ずかしいのですが、すぐに謝っています。

「こうこうこうしたほうが・・・あ、申し訳ありません。これじゃやっぱり動かないです。◯○という理由で××で(略)」

8. 辛い時は辛いと言う

辛いと言わないと辛いんだ、と思ってもらえません。
上司に相談しましょう。
「しんどいです。」みたいな感じで丸投げしてます。
上司さまさまです。感謝しています。

9. 新人にはしっかり喋ってもらう

新人のカテゴリに入ると思われる自分がこれを書くのは少し可笑しい話ですが書いておきます。
これはあんまり個人的な話からは外れます。

自分のタスクの話は自分で喋ってもらいます。(フォローはします。)

何故かと言うとぶっちゃけると新人は喋ることに慣れていません。
しかし、現場・会社の都合上、他の会社の人と喋る機会が多いです。
距離感はなかなか掴みかねるところがあります。
その為に慣れてもらうためにも話をしてもらいます。

慣れてくると自信も付いてくるかと思われます。

あとあまり面倒見れないこともあるので、という話もあるのですが
ここだけの秘密です。(ごめんね後輩)

10. 話が噛み合ってない時は間に一旦入る

話が伝わっていないまま喋っている人がいます。一旦話を仲裁して
もう少し手前から聞いてみます。話を遡ります。

「○○機能の××なんですけどdさおいひゃskljdさkljだs(勢いで喋ってる)」(相手、首をかしげる)
(間に入ります。)「ごめん、えーと、何作ってるんだっけ?」「エラー表示機能です」
「なんで作るんだっけ?」「エラーを表示するために」
「ちょっと待って、なんの為にエラーを表示したいんだっけ?」「入力ミスを防ぐためです」
「で、今どういう状態だっけ?」「エラー表示機能の中でエラー表示機能をどうすればいいのか悩んでます」

質問ベースで会話をして、トラブルシュート的な形で話を聞いたりしています。

個人的に仕様決める時はなんか話を一応聞いておきたいみたいなところで間に入ったりしてます。

11. 会話をする際に内部の技術的な(実装に近い)単語は避けて、資料を見て分かるもので説明する。

現場の都合も大きく関わってくるのですが
現状、コンテキストが分断されがちな状態になっています。
そのために、技術的な単語は避けて、見て分かる物を出したりしています。

「HogeViewからHogeActionが発火して、そこから書き換えが起きてー、画面が少し崩れてー」

上の例はひどいですね。

下のような形で喋ることを意識しています。実装の話は聞かれたら答える感じで。
「画面クリックした時に、別の処理が呼ばれて予期せぬ動作をしています。スクリーンショットとしてはこんな感じです。」

12. 他人の作業を手伝ってあげる。

信頼感構築できます。次からは作業を振っても怒られません(怒られる。)

人間、面倒なことは嫌いです。手伝ってあげると割と好かれます。
手伝いすぎにだけ注意しましょう。

特に人とコミュニケーションを取るのを避ける人がいます。
そういう人の代わりにパッと聞いてきましょう。

(追記)13. わからないことはわからないと言う。

すぐに言わないとろくなことはありません。
また、大抵の人は尋ねると答えてくれます。

(追記)14. チームの誰が何をやっているのか把握しておく。

管理的な側面もあるのですが
これは次の内容に掛かってきます。

(追記)15. 近くにいる人に聞けることで分からないことはできるだけすぐ聞く。

他人が何をやっているのか、やっていたのかを聞いたり知っておくとよいかと思われます。

なにかの案件で改修する話がある場合
仕様や設計については実装担当者に聞いたほうが早いと思います。
実装に関してはコードを見れば分かると思いますが
一旦ザクッと説明してもらうと読みやすいかと思います。

せっかく隣にいるんだから。パッとシュッとお仕事したいですね。

最後に

とりあえず思いつく限りでエンジニアの自分がやっている、心掛けていることを書いてきました。
エンジニアとして働く傍ら、チームリーダーとしての役割をこなしています。
そんなおかげもあってか、契約は長いこと続いています。1年半でしょうか。

メールの文言は少し気を付けよう(文面で見るとやや冷たく見えるので)
みたいな話もあるのですが、これはまぁ置いときます。(割と痛い目見た)

どこまで行っても、僕は、僕達はエンジニアと言えども、人間です。

どれだけ正しくても、受け入れられないタイミングはあります。

僕だって、この記事を読んでいるエンジニアでない方もエンジニアの方もそうかと思います。

逆に人間である以上、嫌な話ですが感情面に漬け込む隙がどこかであるはずです。
少し、うまく使ってみてはどうでしょうか。

新卒2年目のエンジニアの自分がやっている、少し泥臭い話でした。

(追記)
 他人をないがしろにしているわけではなくて
 気持ちよくパッとシュッとお仕事をするための気持ちを書きました。

追記のおまけ

自分は学生のうちにある程度技術を触っていたこともあり
ある程度自信があったため、顧客と話していました。

教育係の方は新人に技術をしっかり教えて
自信をつけさせてあげてはいかがでしょうか。

技術ありきの自信だと思います。

追記のおまけ2

新人には裁量を与えてあげてください。勝手に伸びると思います。
ケアだけはしてあげてください。

Proxyの生成手法を素振りしてみる。

最初に

さて、タイトルどおりなのですが
素振りするだけの記事だったりします。

素振りしたライブラリはこちら


Proxyと言えばjava.lang.reflect.Proxyですね。

今回は次の2つのクラスについて、Proxyを作ってみます。

  public static class Some implements SomeInterface {
    @Override
    public void apply() {
      System.out.println("hello world");
    }

  }
  interface SomeInterface {
    public void apply();
  }

まず前提として、今回書くサンプルコードは全て次の出力文が出力されます。

apply:start
hello world
apply:end

標準API

まずはJDKについてるProxyクラスを使ってProxyを作ってみましょう。

  @Test
  public void testCreateDynamicProxy() {
    SomeInterface impl = new Some();
    SomeInterface proxy = (SomeInterface) Proxy.newProxyInstance(
      this.getClass().getClassLoader(),
      new Class[] {SomeInterface.class},
      (Object _proxy, Method method, Object[] args) -> {
        System.out.println(method.getName() + ":start");
        method.invoke(impl, args);
        System.out.println(method.getName() + ":end");
        return null;
    });
    proxy.apply();
  }

implを生成しています。今回は引数も戻り値も特に使われないので記述は基本的に簡単になっていますね。

ところで、余談ですがラムダに型つけられるんですね。
最近全然使ってなかったので知りませんでした。

ではここからはライブラリを使っていきます。

Javassist

Javassistを使ってみましょう。

  @Test
  public void testCreateProxyWithJavassist() throws NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException,
    InvocationTargetException {
    ProxyFactory factory = new ProxyFactory();

    factory.setSuperclass(Some.class);
    Some some = (Some) factory.createClass()
      .newInstance();

    ProxyObject proxyObject = (ProxyObject) some;
    proxyObject.setHandler((Object self, Method thisMethod, Method proceed, Object[] args) -> {
      System.out.println(thisMethod.getName() + ":start");
      proceed.invoke(self, args);
      System.out.println(thisMethod.getName() + ":end");
      return null;
    });
    some.apply();
  }

Proxy作るためのクラスが用意されていますね。
ByteCode操作をするライブラリに入ってるクラスなので、setSuperClassといった形で、クラスを渡す形になってますね。
Bytecode操作してサブクラスを動的に生成して、メソッドにhandlerの処理とか追加するようですね。

ちなみにProxyFactoryクラスにはwriteDirecotryフィールドがあって
そこにパスを渡してやるとcreateClassした際に.classファイルが生成されます。
生成されたクラスをデコンパイルしてみると面白いかもしれません。

cglib

今度はcglibで書いてみます。
こちらもProxyを作るためのクラスが用意されています。

  @Test
  public void testCreateProxyWithCglib() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Some.class);
    MethodInterceptor interceptor = (Object obj, Method method, Object[] args, MethodProxy proxy) -> {
      System.out.println(method.getName() + ":start");
      proxy.invokeSuper(obj, args);
      System.out.println(method.getName() + ":end");
      return null;
    };
    enhancer.setCallback(interceptor);
    Some some = (Some) enhancer.create();
    some.apply();
  }

こちらもByteCode操作をするライブラリに入ってるクラスなのでなんだか似てますね。
特に難しいことはないかと思われるのですが
Callbackというマーカインターフェースが存在するのですが
その子クラスに、InvocationHandlerとMethodInterceptorというのがあります。

ぱっと見た感じだと引数がすごい似てるので、どっち使えばいいか分かりませんでしたが
superのメソッドを呼びたい、ただただInterceptしたいだけならMethodInterceptorで良さそうですね。

ちなみにCallbackインターフェースのJavadocを見ると分かるのですが
以下のインターフェースがCallbackインターフェースを継承してます。
色々できそうですね。

  • MethodInterceptor
  • NoOp
  • LazyLoader
  • Dispatcher
  • InvocationHandler
  • FixedValue

Byte Buddy

最後はByte Buddyを使ってみます。

少しこいつは毛並みが違います。

まずコードを晒してみます。

  @Test
  public void testCreateProxyWithByteBuddy() throws InstantiationException, IllegalAccessException {
    Some some = (Some) new ByteBuddy().subclass(Some.class)
      .method(ElementMatchers.any())
      .intercept(MethodDelegation.to(TestInterceptor.class))
      .make()
      .load(this.getClass()
        .getClassLoader())
      .getLoaded()
      .newInstance();
    some.apply();
  }

  public static class TestInterceptor {
    @RuntimeType
    public static Object intercept(@SuperCall Callable<?> zuper, @Origin Method method) throws Exception {
      System.out.println(method.getName() + ":start");
      zuper.call();
      System.out.println(method.getName() + ":end");
      return null;
    }
  }

chain methodによる流れるようなインターフェースが特徴的です。

また、いくつかのstaticメソッドを提供するクラスがあるみたいですね。
また、@RuntimeType、や@SuperCall、@Originなどのアノテーションも出現しています。

リフレクションとバイトコード操作を組み合わせた感じのゴリゴリの黒魔術感が出ています。
個人的には今回始めて触ったので、はっきり言ってキモイのですが
すごく発想としては面白いのではないでしょうか。

まとめ

今まで、Javaの標準機能ではインターフェースを定義しないと
Proxy的な形でMethodのinterceptionが行えませんが
ライブラリを使って、ByteCodeを操作することによって、サブクラスを動的に生成し
インターフェースの定義をしなくても、Methodのinterceptionが行えるようになりました。

ちなみに、Spring 4.3.4ではcglibを使ってclass-based proxyを作っているようですね。
ScopedProxyMode (Spring Framework 4.3.5.RELEASE API)

旅行の準備しなきゃ。

とりあえず今日はここまで~

リポジトリはここに置いてます。
github.com

おまけ

Bytecode操作についてはひしだまさんがJavassistについて書いてくださってます。
Javassistメモ(Hishidama's Javassist Memo)

jyukutyoさんがJJUG 2016 Fallで話していたスライドも見てみると面白いです。
JJUG CCC 2016 fall バイトコードが君のトモダチになりたがっている

Bytecode操作を使った、DIコンテナについてのきしださんの記事を読むと非常に面白い上に為になります。
作って理解するDIコンテナ - きしだのはてな

Pluggable Annotation Processor APIでimport文を取る

APT、Pluggable Annotation Processorは皆さんお使いでしょうか。

タイトルの通りですが、APTでimport文を取ってみます。

gradleはとりあえずこんな感じです。
今回はAPTのProcessorを作るプロジェクトですがAPTを使います(グルグル目)

準備

apply plugin: 'java'
apply plugin: "net.ltgt.apt"

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "net.ltgt.gradle:gradle-apt-plugin:0.9"
  }
}

sourceCompatibility = 1.8
repositories {
    jcenter()
}

dependencies {
    compileOnly group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc2'
    compileOnly files("${System.properties['java.home']}/../lib/tools.jar")
    testCompile 'junit:junit:4.12'
}

少し解説:AutoServiceについて

AutoServiceGoogleのライブラリのautoの中の一つです。
ServiceLoaderを楽に扱うためのライブラリです。

今回作るAnnotation ProcessorのコードはServiceLoader経由でロードされて使用されます。

META-INF/servicesの配下にServiceのインターフェース、もしくは抽象クラスのFQCNと同じ名前のファイルに
ロードしたいプロバイダクラス(ざっくりいうと実装クラス)を改行区切りで記述します。
Javadoc見る感じだと#でコメントも書けるそうです。

本来は自分でファイルを記述しないといけないところをAutoServiceを使って楽をします。
@AutoServiceをつけることでMETA-INF/services/(インターフェース名・抽象クラス名)に
自動的にアノテーションを付けたクラスを書いてくれる形になります。

では今回実装したソースはこちら。

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;

import com.google.auto.service.AutoService;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
public class ImportShowProcessor extends AbstractProcessor {

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    roundEnv.getRootElements()
      .stream()
      .map(Trees.instance(this.processingEnv)::getPath)
      .map(TreePath::getCompilationUnit)
      .flatMap((unit) -> unit.getImports()
        .stream())
      .forEach(System.out::println);
    return true;

  }

}

そんなに長くはありませんね。

クラスについてるAnnotationの説明から

まず@AutoServiceですが、これは先ほど説明したAutoServiceですね。
説明は省きます。

@SupportedSourceVersion(SourceVersion.RELEASE_8)は
このAnnotation ProcessorはJava8からサポートしてますよ、というメタ要素ですね。
このアノテーションを付けてない、かつ親クラスの実装を変更してなければ、6からサポートしてます、という形になります。

@SupportedAnnotationTypes("*")
これは全てのコンパイル要素を対象とするようです。

上記二つのAnnotationはそのアノテーション名と似たような名前のメソッドがAbstractProcessorに定義されています。

もう一つSupportedOptionsというアノテーションもあって、こちらはコンパイル時のオプションから値を注入できるような仕組みだそう。
今回は使いません。

processメソッドについて

こちらが今回実装した部分ですね、と言っても
そんな大したことはしてなくて
まぁimport文取り出して、表示してるだけですね。

じゃあこちらのAnnotationProcessorを使ってコンパイルしてみます。
gradleのjarタスクで生成されたjarを使います。

javac -cp lombok-processor-example.jar Test.java

コンパイルするソースはこちらです。

import java.util.List;
import java.awt.*;

class Test{
	List<String> yyy;
	String test;
} 

出力は・・・

import java.util.List;

import java.awt.*;

import文が出ましたね。はい。

というわけで

d.hatena.ne.jp

jdeps使いましょう。

今回書いたソースはこちらに置いてあります。
github.com


参考
fits.hatenablog.com