【Javascript】オブジェクト配列のコピーに関する注意点

こんにちは。システム事業部の鈴野と申します。

今回は、JavaScriptの業務中コーディングした際の問題について、備忘録的としてまとめた内容を掲載したいと思います。

弊社でもフロント側の開発を扱う業務の中でJavaSciptを扱います。 今注目のフレームワーク「React」「Vue.js」についても積極的に活用しております。 JavaScriptに限らず、プログラム言語において「配列」はあらゆる場面で使用しますが、扱う中で苦戦した経験があったのが「配列のコピー」。

プログラムにおけるコピーには以下の2つがあります。

○シャローコピー →コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同一データを参照する。 いわゆる「参照渡し」で、コピー先に変更があればコピー元にも反映される。

○ディープコピー →オブジェクトとメモリ上のデータの両方がコピーされるため、コピー元とコピー先は別のデータとなる。 コピー先の変更はコピー元に反映されない。

例えばオリジナルの配列をコピーし、表示用に加工を行う配列を作る場合、もし表示用配列がシャローコピーの場合 オリジナル配列が上書きされてしまいます。

コピーに関するJavaScriptの基本文法もどんどん対応されているようで、有名なのがES6で追加さえたObject.assign()。

 const assigned = Object.assign(, [ 1, 2, 3, 4, 5, 6 ]);

上記はシンプルな配列ですが、例えば以下のような場合はどうでしょうか。

 const complex= [

  { id: 1, array: [ 1, 2, 3, 4, 5 ,6, 7 ] },

  { id: 2, array: [ 11, 12, 13, 14, 15 ,16, 17 ] },

  { id: 3, array: [ 2, 1 ] },

  { id: 4, array: [ 8, 9, 10, 11, 12 ] },

  { id: 5, array: [ 0, 1, 2, 3, 4, 5 ] },

 ]

Object.assign()は第一引数にを渡すことで第二引数以降の内容を元とした新しい配列を生成しますが 動きとしてはシャローコピーであるため、トップ階層に配列要素がある場合は問題ありませんが complexのようなネストとなると対応し切れません。 ReactReduxのような、新規オブジェクト生成を多用する開発では注意が必要です。

現状対応では「JSON.parse()」を使用するのが簡単な対応と思われます。

const deepCopy = JSON.parse(JSON.stringify(complex));

また、jQueryを使える場合はjQuery.extend()の使用も有用なようです。 ただし、上記のような数値、文字列、配列ではコピーされることを確認できていますが、 配列にDate、functionなどが存在する場合は除去されてしまうようです。 配列内容によって使えない機能であるということにご注意ください。

今後もゼネットでは、上記のような言語関連の独自概念についてより良い対策を考え 共有していけるよう努めてまいります。

Ruby技術者認定試験 Goldを受験しました

はじめまして。システム事業部の天野と申します。

Ruby技術者認定試験Gold(Ruby Association Certified Ruby Programmer Gold version 2.1)を先月受験しました。結果は合格でした!!! 今回は私の受験体験記です。

プロフィール

  • 文系出身
  • 入社2年目
  • Ruby歴約2年
  • Ruby技術者認定試験Silverは2年前に取得

勉強方法

問題集1周目は、1問ずつ解き、答えを確認しました。
2周目は10問ずつなど1回に解く問題を増やしました。
3周目以降は本番同様50問ずつ解き、点数を出すようにしました。
答えを見て納得できなかったものに関して、下記の「使った参考書、HP」の該当項目をチェックします。
そして実際にコンソールで実行してみました。(値を変えたりもしました)

使った参考書、HP

勉強期間

  • 2週間
  • 1日2時間程度

学習のポイント

学習のポイントをいくつか紹介します。

・ メソッドの可視性

privateメソッドについてです。
privateメソッドは外部から呼びだすことが基本的にはできませんが、
サブクラスからスーパークラスのprivateメソッドの呼び出しは可能です。

  class A
    private
    def hoge
      "private A hoge"
    end
  end

  class B < A
    def fuga
      hoge
    end
  end

  B.new.fuga
  # 実行結果
  #=> "private A hoge"

またsuperで親クラスの同名メソッドを呼び出しする際も同様に呼び出せます。

  class A
    private
    def hoge
      "private A hoge"
    end
  end

  class B < A
    def hoge
      super
    end
  end

  B.new.hoge
  # 実行結果
  #=> "private A hoge"

・定数の定義について

rubyでは定数(アルファベット大文字で始まる識別子)を定義することができます。
ただし、メソッド内で定義することができないので注意が必要です。

  def a
    CONST = "AAA"
  end

  #=> SyntaxError: (eval):3: dynamic constant assignment
    CONST = "AAA"
          ^

・lambdaとProcの動きの違い

lamdaとProcには挙動が異なる点があります。
以下の2つをしっかり押さえておくことがポイントです。

returnした後の挙動

lambdaの場合、callで呼び出した後の処理も実行されます。

  def lambda_method
    lambda = lambda { return p "return lambda method"}
    lambda.call
    p "lamda method end"
  end

  lambda_method
  #実行結果
  "return lambda method"
  "lamda method end"

Procの場合、callで呼び出した後の処理は実行されません。

  def proc_method
    proc = Proc.new { return p "return proc method"}
    proc.call
    p "proc method end"
  end

  proc_method
  #実行結果
  #=> "return proc method"

引数の扱い

lambdaの場合、引数に過不足があるとArgumentErrorが発生します。
lambdaの方が引数の数に関して厳密です。

  lambda = -> (arg1, arg2) { p "#{arg1}, #{arg2}" }
  lambda.call(1)
  #実行結果
  #=> ArgumentError: wrong number of arguments (given 1, expected 2)

  lambda.call(1, 2)
  #実行結果
  #=> "10, 11"

  lambda.call(1, 2, 3)
  #実行結果
  #=> ArgumentError: wrong number of arguments (given 3, expected 2)

Procの場合、引数の過不足でエラーが発生しません。
lambdaとは異なり、過不足の部分には[nil]が入ります。

  proc = Proc.new { |arg1, arg2| p "#{arg1}, #{arg2}" }
  proc.call(1)
  #実行結果
  => "1, "

  proc.call(1, 2)
  #実行結果
  => "1, 2"

  proc.call(1, 2, 3)
  #実行結果
  => "1, 2"

・可変長引数と配列展開

[*(アスタリスク)]には種類があります。
それをきちんと区別することがポイントです。
仮引数の前に[*]をつけることで可変長にすることができます。
呼び出す際、以下のように複数の値を入れると配列にしてくれます。

  def foo(*args)
    p args
  end
  
  foo("a", "b", "c")
  #実行結果
  => ["a", "b", "c"]

ややこしかったのがこちらです。
p *args の[*]は配列展開を行うためのものなので以下のようになります。
def foo(*args) の[*]で配列に変換し、p *argsの[*]で配列を展開しています。

  def foo(*args)
    p *args
  end
  
  foo("a", "b", "c")
  #実行結果
  => "a"
  "b"
  "c"

以下のような場合もあります。こちらは
foo(*["a", "b", "c"]) ここの[*]では配列展開されるため、
foo("a", "b", "c")と同様となるので上と同様の結果が得られます。

  def foo(*args)
    p *args
  end
  
  foo(*["a", "b", "c"])
  #実行結果
  => "a"
  "b"
  "c"

まとめると、
仮引数の[*]  ・・・ 可変長引数
メソッド内の[*]・・・ 配列展開
実引数の[*]  ・・・ 配列展開
となります。

他にも
・ inspectとto_sの違い
・ メソッド探索
・ モジュールのincludeやextend,prependの継承チェーン
・ superとsuper()の挙動の違い
・ クラス変数の値
・ Fiberクラス
などRuby歴2年では知らなかったこともたくさんありました。

感想

Silverはメソッドの暗記が主だったような感じがしますが、GoldがRubyの仕組みを理解する必要がありました。
勉強をしていて楽しかったのは圧倒的にGoldです。Rubyを理解する上で良いステップアップの機会になったと思います。 Rubyに興味がある方はまずSilverから挑戦してみてはいかがでしょうか。

Javaで.【ドット】でsplitしたい

お久しぶりです。ゼネットの阿部です。

Javaでの文字列分割で.【ドット】を使ったところうまく行かなかったので、備忘録として記載します。

"北海道,青森,...,沖縄"のようにカンマ編集されている文字列を分割したいということはよくあると思います。

Javaを利用されている方はすぐに思いつくと思いますが、

String[] cities = "北海道,青森,...,沖縄".split(",");  
for(String city : cities){
  System.out.println(city);
}

とすることで

北海道
青森
・
・
・
沖縄

と配列に分割されます。

ここで、文字列が"北海道.青森.,,,.沖縄"のように.【ドット】で分割されている場合に、カンマの場合と同様に処理させます。

String[] cities = "北海道.青森.,,,.沖縄".split(".");  
for(String city : cities){
  System.out.println(city);
}

とすると例外が発生

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0

あれ?分割がうまく行っていない?

ということでOracleDocで調べました。*1

splitの概要には

【この文字列を、指定された正規表現に一致する位置で分割します。】

と記載されていました。

どうやら文字列の.【ドット】が正規表現として判断されたためいずれの文字にも一致しなかったため、分割されていないようだとわかりました。

インターネットで調べてみると、文字を正規表現として意味を持たない文字に変換するためには、

Pattern.quote(".")

とする必要があるとわかりました。

String[] cities = "北海道.青森.,,,.沖縄".split(Pattern.quote("."));  
for(String city : cities){
  System.out.println(city);
}

とすることで

北海道
青森
・
・
・
沖縄

と分割できました

引数で与えた文字が【意味を持たない】文字として利用されるか【正規表現】文字として扱われるのかをしっかり理解しなければ行けないと気付かされた一件でした。

ちなみに余談ですが、

Pattern.quote(".")

の戻り値は

\Q.\E

でした。 これについてはまた今度調べて見たいと思います。

*1:*昔のJavaDocです

追従型メニューの作り方

システム事業部の五十島です。

今回は追従型メニューについて説明していきたいと思います。
追従型メニューとは、スマホサイトでよく見る『スクロールしても画面上に表示されているメニューや広告など』になります。
ちょっと難しそうと思うかもしれませんが、簡単なCSS・HTMLとちょっとだけ頭を使うJSで作れてしまいます。

htmlとCCSは以下のようになります。
構造はシンプルで、idが#init_button#button_down_areaのボタンは最初から表示させておいて、 両方が画面上から見えなくなったら、スクロール用のボタン(.scroll_button_area)を表示させるというものです。

<!-- 初期表示のボタン -->
<button id="init_button">スクロールするボタン</button>

<!-- 実際にスクロールするボタン -->
<div class="scroll_button_area">
  <button>スクロールするボタン</button>
</div>
<div style="height: 500px">
  <!-- スペースを開けるためのタグ -->
</div>

<!-- ボタンのスクロール終了位置 -->
<div id="button_down_area">
  <button>スクロールするボタン</button>
</div>
.scroll_button_area {
  display: none;
}

.show_scroll_button {
  display: block;
  position: fixed;
  bottom: 0px;
  left: 0px;
  width: 100%;
  padding: 0px;
  background-color: #ffffee;
}

JSは以下のようになります。
表示している位置の情報を取得して、div.scroll_button_areaに表示させるクラスを追加して、制御しています。

$(document).ready(function() {
  $(function() {
    $(window).on('scroll', function() {
      var topBtn = $('#init_button');

      // ボタンが表示されていない
      if (!topBtn.size()) { return ; }

      // スクロール中のボタン表示
      var scrollArea = $('.scroll_button_area');

      // 開始位置、終了位置の取得
      var startLine = topBtn.offset().top;
      var endLine = $('#button_down_area').offset().top;

      // 表示画面領域の座標取得
      var scrollTop = $(this).scrollTop();
      var scrollBottom = scrollTop + window.innerHeight;

      // 上部ボタンが画面から非表示、
      // 下部ボタンが画面上に表示されるまで
      if ( scrollTop > startLine && scrollBottom <= endLine ) {
        scrollArea.addClass('show_scroll_button');
      } else {
        scrollArea.removeClass('show_scroll_button');
      }
    });
  });
});

offset()メソッドで、画面全体のどの位置に各ボタンがあるのかを取得します。
ScrollTop()window.innerHeightメソッドで表示領域の上下の座標を取得し、 22行目で各ボタンが表示領域に存在しているかを判定しています。
各ボタンが表示領域外の場合はスクロール用のボタンを表示し、それ以外の時は非表示という制御を行なっています。

JSを改良すれば、表示・非表示のタイミングを好きに変更できるので、改良して使って見てください。

RailsからRDS(MySQL)のDBに接続できなかった話

こんにちは。システム事業部の大柳です。

Rails+MySQLな構成でEC2にデプロイしたときに、DBにつながらず苦戦したので状況と解消方法を書き留めます。

システム構成

  1. Ruby: ver2.4.2

  2. Rails: ver 5.1

  3. MySQL: ver 5.6 (Amazon RDS)

状況

ソースをクローンしてきて、bundle install してrails s で起動を行うところでした。

起動してみると

Mysql2::Error - Access denied for user '******'@'*****' (using password: YES):

どうやらパスワードが間違っているようです。

database.yml をみなおしてみました。

production:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  pool: 5
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  database: <%= ENV['DB_DATABASE'] %>

ENV['XXXX'] とかは環境変数に定義してあるのを拾ってきます。

環境変数を確認してみましたが、ちゃんと設定されていました。

exec $SHELL --login で反映し直しましたがだめでした。

そこで試しにmysqlコマンドでつながるのか試しました。

mysql -u USER -h HOST -p DATABASE

すると

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 233893
Server version: 5.6.39-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

つながりました・・!

以上のことからrails側のDBの設定がどこかしらおかしいことがわかりました。

その後いろいろ試してはみたものの原因が全然わからず・・・・

上司や同僚に電話して助けを乞ったところ

上司「わかったよ」

私「え?!ほんとですか!ありがとうございます!どんな内容でしたか??」

解決したdatabase.ymlがこちら

production:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  pool: 5
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: "<%= ENV['DB_PASSWORD'] %>"
  database: <%= ENV['DB_DATABASE'] %>

結論をいうと、原因はパスワードに「#」が含まれていたためでした。

「#」が含まれていると何がだめなのかというと、yamlファイルのきまりで「#」以降が コメントアウト になります。

つまり「#Password」というパスワードだっとするとrails''空文字と認識します。

そりゃあ何度やってもパスワードが間違っていると怒られるわけです・・・。

超初歩的な問題でした。

次回からは気をつけたいと思います。

SECCON 2018 Online CTF Writeup

お久しぶりです。ゼネットの阿部と申します。 ブログを書くのはかなり久しぶりです。

SECCON2018に参加し、チームとしては合計点203点という悲しい結果に終わりました。

なんとか1問解けたので、紹介しようと思います。

私が担当したのは f:id:zenet-tech:20181029103342p:plain の問題です。

以下のファイルのダウンロードとFLAGを入力するというだけで特段説明もなく、 SECCONにチャレンジするのが始めての私はもう分けがわかりませんでした。 【unzip.zip_26c0cb5b40e9f78641ae44229cda45529418183f】

しかし、始めなければしょうがないということで始めました...

【unzip.zip_...】というファイル形式で、問題もunzipだったので、 拡張子を【.zip】に変えてみて、展開したらどうかと試したところ以下のフォルダに展開されました。 f:id:zenet-tech:20181029103910p:plain

makefile.sh】を見てみると、

echo 'SECCON{'`cat key`'}' > flag.txt
zip -e --password=`perl -e "print time()"` flag.zip flag.txt

どうやら環境変数のkeyをflag.txtに書き込んでそれをzip化しているようです。

ということは、

zip -e --password=`perl -e "print time()"` flag.zip flag.txt

でパスワードに設定している

perl -e "print time()"

がzipのパスワードで、これさえわかれば解けるはずだと思いました。

Perlはほぼ触れていなかったので、インターネットで検索するとPerlでシステム日時を取得し秒変換をしていることが分かりました。

今回のSECCONは 2018/10/27 15:00:00開始だったので

① 【2018/10/27 15:00:00】を秒に変換して【1540620000】で解凍を試みるも失敗。

UTCが原因かと思い、

② 【2018/10/27 6:00:00】【1540534242】で解凍を試みるも失敗。

日時が知りたいと思い、flag.zipのプロパティを開くと f:id:zenet-tech:20181029105030p:plain

このzipファイルが2018/10/27 0:10:42に作られていることがわかりました。

そこで、

③ 【2018/10/27 0:10:42】【1540566642】で解凍を試みるも失敗...

もしかしたら、ファイルのパスワードを取得したタイミングとファイルが生成されたタイミングがずれたのではと推測し、

④ 【2018/10/27 0:10:41】【1540566641】で解凍を試みると成功!!!

所要時間は【40分】ほどで、難易度も低かったため、もともと500点だった得点も【101点】に... まあ解けただけでも良しとしました。なかなか楽しかったです。

来年はもう少し準備をして、時間も確保してから参加しようと思いました。

ISUCON8予選に参加しました!

こんにちは!システム事業部の村田です。

先日開催されたISUCON8予選に、他の社員2名とチーム「ポテチはのり塩派」で3人で初参加しました!

結果としては予選敗退で、再起動試験でfailという悲しい結果・・・でした

最終的なベンチスコアは6894でした

f:id:zenet-tech:20180927182424p:plain

役割としては 私:インフラ 他2人:コード という形で進めました。

そこまでの作業をまとめようと思います。

続きを読む