클래스명, 함수명까지 자동으로 출력해주는 Log class

|

TimeUtil.java

import android.icu.util.Calendar;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class TimeUtil {

  public synchronized static long getTimeAsLong() {
    Calendar calendar = Calendar.getInstance();
    return calendar.getTimeInMillis();
  }

  public synchronized static String getTimeAsString(String format) {
    Date date = new Date(getTimeAsLong());
    SimpleDateFormat sdf = new SimpleDateFormat(format,
        Locale.getDefault());

    return sdf.format(date);
  }

  public synchronized static Long getTimeAsLong(String format, String text) {
    try {
      SimpleDateFormat sdf = new SimpleDateFormat(format,
          Locale.getDefault());
      Date date = sdf.parse(text);
      return date.getTime();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return Long.valueOf(-1);
  }

  public synchronized static String getTimeAsString(String format, long time) {
    Date date = new Date(time);
    SimpleDateFormat sdf = new SimpleDateFormat(format,
        Locale.getDefault());

    return sdf.format(date);
  }
}


Log.java

public class Log {

  private static final String TAG = "ActionManager";
  private static final String PREFIX = "[ram]";

  public synchronized static void v(String message) {
    android.util.Log.v(TAG, getDecoratedLog(message));
  }

  public synchronized static void d(String message) {
    android.util.Log.d(TAG, getDecoratedLog(message));
  }

  public synchronized static void i(String message) {
    android.util.Log.i(TAG, getDecoratedLog(message));
  }

  public synchronized static void w(String message) {
    android.util.Log.w(TAG, getDecoratedLog(message));
  }

  public synchronized static void e(String message) {
    android.util.Log.e(TAG, getDecoratedLog(message));
  }

  private static String getDecoratedLog(String message) {
    StackTraceElement ste = Thread.currentThread().getStackTrace()[4];

    StringBuilder sb = new StringBuilder();
    sb.append(PREFIX);
    sb.append(" [");
    sb.append(TimeUtil.getTimeAsLong());
    sb.append(" ");
    sb.append(ste.getFileName().replace(".java", ""));
    sb.append("::");
    sb.append(ste.getMethodName());
    sb.append("] ");
    sb.append(message);
    return sb.toString();
  }
}

만약 Line Number까지 출력하고 싶으면 ste 변수의 getLineNumber() 메소드를 사용하면 됩니다.

try-finally 보다는 try-with-resources를 사용하자.

|

예외 처리를 위해 사용하는 try - finally 구문은 강력하긴 하지만, try가 2개 이상 사용되거나 그 외의 이유 등으로 코드가 복잡하게 될 수 있는 단점이 있습니다. Java 7.0 부터 지원하는 try with resources를 사용하면 코드를 훨씬 깔끔하게 구현할 수 있습니다.

try-finally 예시

public class FileUtil {

  public static String getFirstLineOfFile(String filepath) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(filepath));
    try {
      return br.readLine();
    } finally {
      br.close();
    }
  }
}


2개 이상의 try가 사용될 경우

public class FileUtil {

  private static final int BUFFER_SIZE = 1024;

  public static void copy(String src, String dest) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
      OutputStream out = new FileOutputStream(dest);
      try {
        byte[] buf = new byte[BUFFER_SIZE];
        int n;
        while ((n = in.read(buf)) >= 0) {
          out.write(buf, 0, n);
        }
      } finally {
        out.close();
      }
    } finally {
      in.close();
    }
  }
}


try-with-resources 예제

public class FileUtil {

  private static final int BUFFER_SIZE = 1024;

  public static String getFirstLineOfFile(String filepath) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
      return br.readLine();
    }
  }

  public static void copy(String src, String dest) throws IOException {
    try (InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dest);) {
      byte[] buf = new byte[BUFFER_SIZE];
      int n;
      while ((n = in.read(buf)) >= 0) {
        out.write(buf, 0, n);
      }
    }
  }
}


catch 사용 예제

public static String getFirstLineOfFile(String filepath) {
  try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
    return br.readLine();
  }
  catch (IOException e) {
    e.printStackTrace();
    return "";
  }
}

Fragment 템플릿 코드

|

Android Studio에서 자동으로 생성해주는 Fragment 템플릿 코드입니다. Factory Method 형태(Factory Method 디자인패턴이 아님)로 되어 있으며, Fragment가 attach 될 때 자동으로 OnFragmentInteractionListener가 등록되도록 되어 있습니다.


public class BlankFragment extends Fragment {

  private static final String ARG_PARAM1 = "param1";
  private static final String ARG_PARAM2 = "param2";

  private String mParam1;
  private String mParam2;

  private OnFragmentInteractionListener mListener;

  public BlankFragment() {
    // Required empty public constructor
  }

  public static BlankFragment newInstance(String param1, String param2) {
    BlankFragment fragment = new BlankFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
      mParam1 = getArguments().getString(ARG_PARAM1);
      mParam2 = getArguments().getString(ARG_PARAM2);
    }
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_blank, container, false);
  }

  public void onButtonPressed(Uri uri) {
    if (mListener != null) {
      mListener.onFragmentInteraction(uri);
    }
  }

  @Override
  public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
      mListener = (OnFragmentInteractionListener) context;
    } else {
      throw new RuntimeException(context.toString()
          + " must implement OnFragmentInteractionListener");
    }
  }

  @Override
  public void onDetach() {
    super.onDetach();
    mListener = null;
  }

  public interface OnFragmentInteractionListener {

    // TODO: Update argument type and name
    void onFragmentInteraction(Uri uri);
  }
}

생성자에 매개변수가 많으면 Builder를 사용하자

|

보통 하나의 메소드가 가지는 매개변수의 개수를 최대 4개 정도까지가 사용하기 무난한 메소드라고 합니다. 그 이상이 될 경우, 해당 메소드를 사용하는 사용자에게 혼란이나 실수를 발생시키기 쉽기 때문에 매개변수를 줄이는 것을 권장합니다.

매개변수를 줄이는 방법으로는 보통 다음 3가지 방법이 있습니다.

  • 매개 변수를 쪼갤 수 있도록 메소드를 여러 개로 분할한다.
  • 매개 변수를 포함하는 헬퍼 클래스를 하나 생성해서 매소드의 파라메터로 헬퍼 클래스를 사용한다.
  • Builder를 사용한다.

여기서는 세 번째 방법인 Builder 사용법에 대한 예시를 알아봅니다.


매개변수가 많은 클래스 예시

public class Robot {

  private final String name;
  private final String nickName;
  private final String controlServerUrl;
  private final int width;
  private final int height;
  private final int x;
  private final int y;
  private final double orientation;

  public Robot(String name, String nickName, String controlServerUrl, int width, int height, int x,
      int y, double orientation) {
    this.name = name;
    this.nickName = nickName;
    this.controlServerUrl = controlServerUrl;
    this.width = width;
    this.height = height;
    this.x = x;
    this.y = y;
    this.orientation = orientation;
  }

  public Robot(String name, String nickName, String controlServerUrl, int width, int height, int x,
      int y) {
    this(name, nickName, controlServerUrl, width, height, x, y, 0.0f);
  }

  public Robot(String name, String nickName, String controlServerUrl, int width, int height) {
    this(name, nickName, controlServerUrl, width, height, 0, 0);
  }
}

이 경우 해당 클래스를 생성할 때 코드를 작성하거나 읽기가 어려워지며 버그가 발생하기 쉬워집니다.


JavaBeans 패턴 적용

고전적으로 사용하던 JavaBeans 패턴을 이용해서 각 매개변수의 setter를 생성해주는 방법도 있습니다.

package builder;

public class Robot {

  private String name;
  private String nickName;
  private String controlServerUrl;
  private int width;
  private int height;
  private int x;
  private int y;
  private double orientation;

  public void setName(String name) {
    this.name = name;
  }

  public void setNickName(String nickName) {
    this.nickName = nickName;
  }

  public void setControlServerUrl(String controlServerUrl) {
    this.controlServerUrl = controlServerUrl;
  }

  public void setWidth(int width) {
    this.width = width;
  }

  public void setHeight(int height) {
    this.height = height;
  }

  public void setX(int x) {
    this.x = x;
  }

  public void setY(int y) {
    this.y = y;
  }

  public void setOrientation(double orientation) {
    this.orientation = orientation;
  }
}

이 경우 인스턴스를 생성하기 위해서는 다음과 같은 코드를 사용합니다.

Robot robot = new Robot();
robot.setName("SuperBot");
robot.setNickName("HiBot");
robot.setHeight(50);
robot.setWidth(30);
robot.setX(10);
robot.setY(20);
robot.setOrientation(250.0f);

하나의 인스턴스 생성을 위해 메소드를 여러 번 호출해야 하기 때문에 객체가 완전히 생성하기 전까지 일관성(Consistency)을 보장받지 못하게 되었습니다. 또한 더 이상 final 키워드를 사용할 수 없기 때문에, 불변성을 갖지 못해 Thread Safety를 보장할 수가 없습니다.


Builder 패턴 적용

Builder 패턴을 적용하면 다음과 같습니다.

public class Robot {

  private final String name;
  private final int width;
  private final int height;
  private final int x;
  private final int y;
  private final double orientation;

  public static class Builder {

    private final String name;  // 필수 매개변수

    private int width = 0;
    private int height = 0;
    private int x = 0;
    private int y = 0;
    private double orientaiton = 0.0f;

    public Builder(String name) {
      this.name = name;
    }

    public Builder width(int value) {
      width = value;
      return this;
    }

    public Builder height(int value) {
      height = value;
      return this;
    }

    public Builder x(int value) {
      x = value;
      return this;
    }

    public Builder y(int value) {
      y = value;
      return this;
    }

    public Builder orientation(double value) {
      orientaiton = value;
      return this;
    }

    public Robot build() {
      return new Robot(this);
    }
  }

  private Robot(Builder builder) {
    name = builder.name;
    width = builder.width;
    height = builder.height;
    x = builder.x;
    y = builder.y;
    orientation = builder.orientaiton;
  }
}


사용 예제는 다음과 같습니다.

Robot robot = new Robot.Builder("SuperBot").height(30).width(20).x(100).y(100)
        .orientation(250.0f).build();

Mojave에서 pygame이 제대로 수행안되는 현상 해결하기

|

Mojave에서 pygame의 충돌

Sierra에서는 아무런 문제가 없었는데 Mojave로 업그레이드한 뒤 python3의 pygame이 제대로 호환되지 않는 문제가 있습니다. 이는 brew를 이용해서 설치한 python3에서 발생하는 문제이기 때문에, 공식 홈페이지를 통해 python3를 다운로드하면 해결이 됩니다.