Amazon LightsailのインスタンスにSonarQubeを立ち上げる

趣味で作っているコードにSonarQubeの静的コード解析を組み込みたいなと思った。 Amazon Lightsailが最近東京リージョンに対応したので、インスタンスを立ち上げてそこにSonarQubeを構築してみることにした。

amazonlightsail.com

Amazon Lightsailを使ってみて

  • AWS定量課金なのに対して、月あたりの定額なので個人利用するときは使い過ぎを心配せずに使える。
  • ただスペックに対してちょっと高めかも?(https://amazonlightsail.com/pricing/)
  • 操作が簡単で、「インスタンスを選ぶ -> keypairをダウンロードする -> スペックを選ぶ -> 名前を付ける」だけで終わる。
  • GUI上からssh可能(セキュリティ上、鍵がなくてもsshできることになるので不安になるが…)
  • 細かい設定ができないので、サービス向きではなさそう。

SonarQubeのインストー

# Java ダウンロード(Java7から8へ)

$ yum install java-1.8.0-openjdk.x86_64
$ alternatives --config java
$ yum install java-1.8.0-openjdk-devel.x86_64

# MySQL ダウンロード

$ yum -y install http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
$ yum repolist enabled | grep "mysql.*-community.*"
$ yum info mysql-community-server
$ yum -y install mysql-community-server
$ mysql --version
$ chkconfig mysqld on
$ service mysqld start
$ mysql_secure_installation

# MySQLにDB,User作成

mysql> CREATE DATABASE sonar CHARACTER SET utf8 COLLATE utf8_general_ci;
mysql> CREATE USER 'sonar' IDENTIFIED BY 'xxx';
mysql> GRANT ALL ON sonar.* TO 'sonar'@'%' IDENTIFIED BY 'xxx';
mysql> GRANT ALL ON sonar.* TO 'sonar'@'localhost' IDENTIFIED BY 'xxx';
mysql> exit

# SonarQube インストール

$ wget https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-6.4.zip
$ mv ./sonarqube-6.4.zip /opt/
$ cd /opt/
$ unzip ./sonarqube-6.4.zip
$ ln -s sonarqube-6.4 sonar
$ cd /opt/sonar/conf
$ vim sonar.properties // jdbc設定
$ cd /opt/sonar/bin/linux-x86-64
$ ./sonar.sh start

# Nginx インストール

$ yum install -y nginx
$ vim /etc/nginx/nginx.conf // 9000番ポートへproxy設定

【Clean Code】Chapter3 Functionsのメモ

Clean Codeという本を読み始めた。

www.amazon.co.jp

友人から譲っていただいたもので(値段が高い。。) 英語で書かれたものなので、読むのに時間がかかるかもしれないが、 チームのコードレビューで活かせそうなので、読みながらメモしていく。

Small!

  • functionは短ければ短いほど良い。
  • かのKent BeckがSparkleで書いたコードは、全てのfunctionが3~5行くらいで書かれていた。(ので、それくらいで書くのが望ましい)

Do One Thing

  • functionは1つのことをしろ…というが、1つのこととは何だろう。
  • 抽象レベルを1つだけ上げてfunctionの役割を説明した時に、一言で済むようにするのが良い。(実際の仕事としては1つではなくて良い)
  • 細かく分けすぎてもだめ。同じような実装が存在する別のfunctionが存在する場合は、そのfunctionの役割を考え直す。
  • functionの中の処理をsectionで分けるケースも存在する。

One Level of Abstraction per Function

  • 実装する時は、書いている処理がcore conceptなのか、detail implementionなのか区別すること。
  • 一つのfunctionの中に書く処理は、core conceptとdetail implementionは一緒に書くべきじゃない。

Switch Statement

  • switch構文は常に冗長になりがちでDo One Thingの法則を守ることができない。
    1. コード量が多く、Employee typeが増えるにつれて肥大化していく。
    2. 一つ以上のことを指定いるし、単一責任原則を破っている
    3. 開放/閉鎖原則を破っている
    4. 似たようなfunctionがたくさん生成されてしまう。
  • -> Factoryパターンで対応しよう。(ポリモフィズムを使用しよう)
public Money calculatePay(Employee e) throws InvalidEmployeeType {
    switch (e.type) {
        case COMMISSIONED:
            return calculateCommisionedPay(e);
        case HOURLY:
            return calculateHourlyPay(e);
        case SALARIED:
            return calculateSalaliedPay(e);
        default:
            throw new InvalidEmployeeType(e.type);
    }
}
public abstract class Employee {
  public abstract boolean isPayday();
  public abstract boolean Money calculatePay();
  public abstract void deliverPay(Money pay);
}
-----------
public interface EmployeeFactory {
  public Employee makeEmployee(EmployeeRecord r) thorws InvalidEmployeeType;
}
-----------
public class EmployeeFactoryImpl implements EmployeeFactory {
  public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
    switch (r.type) {
        case COMMISSIONED:
            return new CommissionedEmployee(r);
        case HOURLY:
            return newHourlyEmployee(r);
        case SALARIED:
            return new SalaliedEmployee(r);
        default:
            throw new InvalidEmployeeType(r.type);
    }
  }
}

Use Descriptive Names

  • 読み手の期待通りの動作をするように名前をつける
  • 名前が長いことを恐れるな。謎の短い名前より長い名前の方がずっといい。
  • 名前を考えることに時間を費やすことを恐れるな
  • 名前の付け方は、統一させよう。

Function Arguments

  • 引数の数は0~2に抑える。3は可能な限り避ける。4以上は基本ないと考えよう。
  • テスト観点でも3つ以上の引数を考慮するのは大変でしょう?
  • 引数はなし or 単項であることが理想。
  • 2項であることは理由があればOK(Point p = new Point(0, 0);など)

Flag Arguments

  • flag、ダメ絶対。
  • flagをつけることで、確実にfunctionは1つ以上のことをすることになるし、何よりもmethodを複雑化する要因になる。
  • render(boolean isSuite)とするくらいなら、renderForSuite()renderForSingleTest()に分けるべき。

Verbs and Keywords

  • write(name)よりはwriteField(name)
  • assertEquals(expected, actual)よりはassertExpectedEqualsActual(expected, actual)

Have No Side Effects

  • functionの名前から期待される動作とは別に副作用を持っているコードは撤廃しよう。障害につながりやすい。
public class UserValidator {
    private Cryptographer cryptographer;
    
    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.Null) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize();
                return true;
            }
        }
    }
}

Output Arguments

  • 可読性を考慮すると、Output Argumentsはない方がいい。
  • appendFooter(s)よりはreport.appendFooter()

Commnad Query Separation

  • functionはオブジェクトの状態を変更する or オブジェクトの情報をreturnする。両方はやらない。

Prefer Exceptions to Returning Error Codes

if (deletePage(Page) == E_OK) {
    ...
} else {
    ...
}
try {
    deletePage(Page);
    ...
} catch (Exception e) {
    ...
}

Extract Try/Catch Blocks

  • try…catch文は分けた方が読みやすい、というだけの話。
public void delete(Page page) {
    try {
        deletePageAndAllReferences();
    }
    catch(Exception e) {
        ...
    }
}

private void deletePageAndAllReferences(Page page) throws Exception {
    ...
}

Don’t Repeat Yourself

  • 同じコードを2箇所に書かない。

Structured Programming

  • inputとoutputは1つだけ、が望ましい。
  • できる限りreturnは複数書かない。
  • break, continue, gotoなどもできるだけ書かない。

GuavaのImmutable Collectionsの実装サンプル

GuavaのImmutable Collectionsを見てみる。 (Java 9 のCollections APIでも同様のstatic factory methodが実装予定)

一般にImmutableであると、参照効率が良い, スレッドセーフなどのメリットがある。 github.com

以下、サンプル。

public static void main( String[] args )
{
    // static factory patternで宣言
    List<String> signals = ImmutableList.of("red","yellow","green");
    signals.add("pink"); // throw new UnsupportedOperationException


    // 初期化時の処理はbuilderも使用可能
    ImmutableList.Builder<String> pokemonBuilder = ImmutableList.builder();
    pokemonBuilder.add("zenigame");
    pokemonBuilder.add("hitokage", "hushigidane"); // 可変長引数も代入可能

    List<String> pokemonAtGoldAndSilver = Lists.newArrayList("waninoko", "hinoarashi", "tikorita");
    pokemonBuilder.addAll(pokemonAtGoldAndSilver); // Iterable型も代入可能

    List<String> pokemon = pokemonBuilder.build();
    pokemon.add("pikachu"); // throw new UnsupportedOperationException
}

Jinja2を試してみた(Python テンプレートエンジン)

PythonのテンプレートエンジンであるJinja2を使って、 信号機の色を出力する簡単なサンプルを作ってみる。

Welcome | Jinja2 (The Python Template Engine)

インストー

環境はMBPローカル。pipですぐにインストールできる。

$ pip install jinja2

実装サンプル

signals.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
tmpl = env.get_template('tmpl.txt')

signals = []
signals.append({'color':'blue'})
signals.append({'color':'yellow'})
signals.append({'color':'red'})

output = tmpl.render({'title':'signal color', 'signals':signals})

f = open('output.txt', 'w')
f.write(output)
f.close()

tmpl.txt

{{ title }}
{% for signal in signals -%}
 - {{ signal.color }}
{% endfor  %}

実行結果

output.txt

signal color
- blue
- yellow
- red

Spring Security 実装サンプル

Spring Securityのチュートリアルをやってみたのでメモ。 最低限必要なものは主に3つ。

1. Spring Security設定クラス

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 認可対象外の設定
        web.ignoring().antMatchers("/favicon.ico", "/css/**", "/js/**", "/img/**"); 
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        // mypage以下は認可対象とする。
        http.authorizeRequests()
                .antMatchers("/mypage**")
                .authenticated();

        // ログイン設定
        http.formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/loginForm")
                .failureUrl("/loginForm?error")
                .defaultSuccessUrl("/", true)
                .usernameParameter("username")
                .passwordParameter("password");

        // 認証用cookie1ヶ月保持
        http.rememberMe()
                .tokenValiditySeconds(86400);

        // /logoutにpostするだけでログアウト
        http.logout()
                .logoutUrl("/logout**")
                .logoutSuccessUrl("/loginForm");
    }

    @Configuration
    static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
        @Autowired
        AccountDetailsService accountDetailsService;

        @Autowired
        PasswordEncoder passwordEncoder;

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(accountDetailsService)
                    .passwordEncoder(passwordEncoder);
        }
    }

}

2. 認証オブジェクトクラス

public class AccountDetails implements UserDetails {
    private final User user;

    public AccountDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList("DEMO");
    }

    public User getUser() {
        return user;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getEmail();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

3. 認証オブジェクト生成, チェッククラス

@Service
public class AccountDetailsService implements UserDetailsService {

    @Autowired
    AccountRepository accountRepository;

    @Autowired
    ExceptionProvider exceptionProvider;

    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return Optional.ofNullable(accountRepository.findOne(email))
            .map(AccountDetails::new)
            .orElseThrow(() -> new UsernameNotFoundException(
                "user specified by " + email + " is not found!"
            ));
    }
}

Spring Securityの概要

Spring Securityについて勉強した時のメモ。 ほぼTERASOLUNA Projectのドキュメントの内容です。 9.1. Spring Security概要 — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.3.0.RELEASE documentation

概要

  • Spring Securityとは、Webアプリケーションにセキュリティ対策機能を実装するときに利用されるフレームワーク
  • 基本機能として大きく分けて次の2つの機能を持つ。
    • 認証:ユーザの正当性を確認する作業。
    • 認可:アプリケーションが提供するリソースや処理に対して、アクセスを制御する機能。
  • 他にもセキュリティ強化のため下記の機能を持つ。
    • セッション管理機能:セッションのライフサイクル管理, セッションハイジャック対策, セッション固定攻撃対策
    • CSRF対策機能:CSRF対策
    • ブラウザセキュリティ機能連携:レスポンスヘッダによってブラウザが提供するセキュリティ機能と連携する

フレームワーク処理

  • FilterChainProxy

    • フレームワーク処理のエントリーポイント
    • サーブレットフィルタークラス
    • 全体的な処理の流れの制御をしており、実際のセキュリティ対策処理は SecurityFilterChain が行なっている。
  • HttpFireWall

    • HttpServletRequestHttpServletResponseに対してファイヤーウォール機能を提供するもの。
    • DefaultHttpFireWallにはディレクトリトラバーサル攻撃やHTTPレスポンス分割攻撃に対するチェックなどが実装されている。
  • SecurityFilterChain

    • FilterChainProxyが受け取ったリクエストに対して、適用するSecurity Filterの管理をするインターフェイス
    • リクエストURIによって、適用するSecurity Filterの管理をすることができる。
    • SecurityFilterクラスの種類
      • SecurityContextPersistenceFilter:リクエストを跨いでユーザの認証情報を共有する機能を提供するクラス。デフォルトではHttpSessionに認証情報を格納して実現している。
      • UsernamePasswordAuthenticationFilter:リクエストパラメータで指定されたユーザ名とパスワードを用いて認証処理を行うクラス。
      • LogoutFilter:ログアウト処理を行うクラス
      • FilterSecurityInterceptor:HTTPリクエスト(HttpServletRequest)に対して認可処理を実行するクラス。
      • ExceptionTranslationFilter:FIlterSecurityInterceptorで発生した例外をハンドリングするクラス。

Spring BootのMethod Validation

@ControllerクラスのPathVariableを、Spring Bean ValidationのMethod Validationで行った。 当初@Configurationクラスに下記のような設定が必要かと思われたが、公式リファレンスを見ると必要ないみたい。 (Spring boot 1.5.2.RELEASE version以上で必要ないことを確認している)

@Configuration
public AppConfig {

    @Bean
    LocalValidatorFactoryBean localValidatorFactoryBean() {
        return new LocalValidatorFactoryBean();
    }

    @Bean
    MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
        methodValidationPostProcessor.setValidator(localValidatorFactoryBean());
        return methodValidationPostProcessor;
    }
}

Method Validationは↓のように、クラスに対して@Validatedアノテーションを追加するだけで良い。 Spring Boot Reference Guide

@Controller
@Validated
public class CategoryController {

    @RequestMapping(value = "/{hoge}")
    public String showHoge(@PathVariable("hoge") @Size(min = 8, max = 10) final String hoge) {
        return "hoge";
    }
}