[Android]신한은행 프로리그 앱 리뉴얼 1.1 버전 내용

                 

새해 계획 중 하나인 자작앱 스타크래프트 “신한은행 프로리그 10-11“앱을

1.1 버전으로 리뉴얼 하는 세부내용을 세워보았다.

  • 프로리그/팀/개인리그 결과 잘못나오는 버그패치
  • 버전업데이트 액티비티 추가
  • admob 달기
  • minor 버그패치/성능개선

일단 admob사이트에 계정을 만들고, 내가 만든 앱을 연결시키는건 완료!

 

너무 커다란 업데이트 계획을 세워서 하려니 벌써 2월 중순인데도 아직 업데이트를

못하고 있다. 이번주에 달려서 1.1버전 업데이트를 해보자꾸나! ^^

아~ 앱 이름도 바꿔야 함 ㅠㅠ

*안드로이드 마켓  “신한은행 프로리그 10-11

광고

[Java]if-else 리팩토링

오늘 회사에서 업무중에 아무 생각없이 if-else 중첩으로 코딩하고 “기능적으로”  잘

돌아가는 것을 확인한 뒤, 해당 업무를 접으려고 했습니다.

그런데 문득 이런 생각을 해보았습니다.

왜 맨날 분기로직은 if-else로만 짜야할까… 과연 내가 아닌 다른 사람이

내 코드를 유지보수한다면 저걸 얼마나 이해하고 작업할 수 있을까

그래서 과감하게 if-else 로직을 걷어내고 리팩토링 하기로 결정하고 구글링을 통해 그런

사례가 있는지 찾아서 테스트코드를 만들었습니다.

아래 참고 url은 꼭 한번 읽어보시기 바랍니다.

소소코드참고 url에서 다운받아서 인터페이스를 구현한 enum class의 메서드에서

String Object를 리턴하도록 약간 수정하였습니다.

1. 먼저 기존 if-else class입니다.

public class IfThenElse {

    public String invoke(String operationName) {
    	String result = null;

        if (StringUtils.equals(operationName, "operation1")) {
            result = operation1();
        } else if (StringUtils.equals(operationName, "operation2")) {
        	result = operation2();
        } else if (StringUtils.equals(operationName, "operation3")) {
        	result = operation3();
        }

        return result;
    }

    public String operation1() {
    	return "operation_1";
    }

    public String operation2() {
    	return "operation_2";
    }

    public String operation3() {
    	return "operation_3";
    }
}

2. 두번째로 if-else 분기문을 enum 클래스로 리팩토링한 코드입니다.

public class NewIfThenElse {

    private interface IOperation {
        String apply(String name);
    }

    private enum Operation implements IOperation {

        OPERATION_1("operation1") {
            public String apply(String name) {
                return "operation_1";
            }
        },
        OPERATION_2("operation2") {
            public String apply(String name) {
            	return "operation_2";
            }
        },
        OPERATION_3("operation3") {
            public String apply(String name) {
            	return "operation_3";
            }
        };

        private static Map<String, Operation> requestLookup;

        static {
            requestLookup = new HashMap<String, Operation>(3);
            requestLookup.put(OPERATION_1.getName(), OPERATION_1);
            requestLookup.put(OPERATION_2.getName(), OPERATION_2);
            requestLookup.put(OPERATION_3.getName(), OPERATION_3);
        }

        private final String name;

        private Operation(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "Operation{" + "name='" + name + '\'' + '}';
        }

        public static Operation getOperationByName(String name) {
            return requestLookup.get(name);
        }
    }

    public String invoke(String operationName) {
    	String result = null;
        Operation operation = Operation.getOperationByName(operationName);
        if (operation != null) {
            result = operation.apply(operationName);
        }

        return result;
    }
}

3. 마지막으로 저 두개의 클래스를 단위테스트하는 코드입니다.

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * if-else Refactoring 테스트케이스
 *
 * @author bluepoet
 *
 */
public class IfElseRefactoringTest {
	Logger logger = LoggerFactory.getLogger(IfElseRefactoringTest.class);
	private String result;

	@Test
	public void procedural() {
		IfThenElse obj = new IfThenElse();

		result = obj.invoke("operation2");
		logger.debug("결과 : {}", result);
		assertThat(result, is("operation_2"));
	}

	@Test
	public void refactor() {
		NewIfThenElse obj = new NewIfThenElse();
		result = obj.invoke("operation1");

		result = obj.invoke("operation1");
		logger.debug("결과 : {}", result);
		assertThat(result, is("operation_1"));
	}

}

단위테스트 결과는 물론, 녹색막대가 뜨며 두개의 메서드 모두 통과로 나옵니다.

로그도 위와같이 console창에서 출력됩니다.

이번에 리팩토링을 진행하면서 기존의 if-else문 자체를 완벽히 거둬내지는 못했지만,

관심대상을 별도의 클래스로 분리해 독립적으로 구현하는 것에는 일부분 성공했습니다.

OOP적인 부분에서는 절반의 성공은 했다고 생각됩니다.

그리고 기존의 코드를 최대한 안 건드리려고 한 부분도 일부분은 만족스럽게 구현했구요.

이것도 예제의 일부분일 뿐이고, 아마 더 좋은 구현사례가 많이 있을 것입니다.

혹시 좋은 리팩토링 사례가 있다면 댓글 남겨주시구요.

오늘 회사 선배개발자님과 커피를 마시며 한 대화중에 떠오르는 말이 있네요

리팩토링은 새로운 기능을 개발할 때 할 수 있는게 아니라 비교적 여유가 있을 때,

기존의 코드를 고치며 하는 것이 더 바람직하다. 그리고 오히려 그렇게 리팩토링을

하는 것이 실력향상에 더 도움이 된다.

리팩토링의 길은 멀고도 험하지만, 그것을 해냈을 때 프로그래머로써 느끼는 “쾌감“은

다른 무엇과도 비교할 수 없는 달콤한 열매겠죠? ^^

*참고 url

[Eclipse]생산성 향상 – 템플릿 등록하기

이클립스를 사용하다 보면 항상 자주 사용하는 소스코드가 있습니다.

예를 들면, 로거를 가져올때 사용하는 아래와 같은 코드죠.

Logger logger = LoggerFactory.getLogger(Test.class);

이걸 매번 클래스 만들때마다 저 소스를 입력하면 시간낭비겠죠?

이럴때 생산성향상을 위해 사용하는 것이 이클립스의 “템플릿“기능입니다.

템플릿“을 등록하고,  “ctrl+space“로 해당구문을 쉽게 입력하실 수 있습니다.

템플릿을 등록하는 방법은 아래 참고url에서 보시면 됩니다. 잘 설명되어 있습니다.

이제 조금 더 빨리 코드를 만들어보시죠?^^

*참고 url

[독후감]기욤뮈소 – 천사의 부름

종이여자“를 작년에 읽고 올해 기욤뮈소의 신작 “천사의 부름“을 완독했습니다.

 

일단, 기존 작품들과 다른 점은 기욤뮈소의 작품에서 꼭 나오던 “판타지(fantasy)“가

없다는 점입니다.

책장을 넘길수록 언제 “판타지“요소가 나올까 나름 기대하면서 봤지만 에필로그까지

판타지는 끝내 나오지 않았습니다. 이번엔 철저히 리얼리티로 간거죠.

사실, 전 이게 더 좋습니다. 어느때는 너무 현실과 동떨어진 판타지가 나와서 스토리에

집중하지 못하게 하는 점이 없지 않아 있었거든요.

그리고 또 하나 다른 점은, 기존의 감성멜로중심의 전개에서 이번 작품에는

스릴러“요소를 추가했다는 것입니다. 그리고 이 “스릴러“의 요소는 이 작품을

긴장감있고 흥미진진하게 만드는데 결정적인 역할을 하게 됩니다.

기욤뮈소의 작품에서 이 “스릴러“요소로 사람을 죽이는 부분이 나온다면 더 이상

말할 필요가 없는거죠.

 

다른 점만 있는 건 아닙니다. 주제 자체는 역시나 “감성멜로“였고, 결말도 또한

해피엔딩“이었습니다.

그리고, 기욤뮈소의 작품에서 빼놓을 수 없는 중요한 부분 한가지!

바로 “주변에 대한 객관적이고도 세밀한 묘사“입니다.

사람 뿐만 아니라 이야기를 끌어가는 내내 등장하는 동물, 장소, 음식, 사물등

기욤뮈소만의 세밀한 묘사로 그려가는 이야기를 보는 것은 눈이 굉장히

즐거워지는 일입니다. 더군다나 작품에 등장하는 장소를 실제로 가서 보고

확인한다면 이 책을 다 읽고 난 뒤의 남는 그 진한 감정의 여운은 아마

쉽사리 잊혀지지 않고 오랫동안 마음 속에 간직되어 있을 것입니다.

 

이제 “여보고마워“, “천사의 부름” 같은 가벼운 책을 읽었으니 다시 무거운

주제의 책으로 돌아갈까 합니다.

다음에는 또 어떤 모습의 책으로 우리를 만나게 될지…

기욤뮈소“는 제가 참으로 좋아하는 멜로작가 중 한명입니다^^

 

ps. 그리고 “천사의 부름“이란 어떤 뜻인지 궁금하시죠???

그건 바로 “운명“이라는 뜻이라고 하네요~!

그리고 마지막 에필로그에 나오는 구절을 잊을 수가 없네요…

인생의 가장 아름다운 날들은 우리가 아직 살지 않은 날들이다

[Spring]@Async 어노테이션 사용하여 비동기로 메서드 실행하기

저번에 회사에서 애플의 apns를 이용해 각각 애플 디바이스로 푸시를 할 수 있는

기능을 만들었습니다.

알람기능은 굳이 “동기“방식으로 구현할 필요가 없었기 때문에 “비동기“방식으로

구현했고, 그때 사용한것이 @Async 어노테이션이었습니다.

@Async에 대한 레퍼런스 문서는 링크를 참조하시면 됩니다.

사용방법은 무척이나 간단합니다.

1. 먼저 비동기로 실행할 놈을 빈으로 설정합니다.

<task:executor id="testExecutor" pool-size="10"/>

<task:annotation-driven executor="testExecutor"/>

pool-size는 각각 상황에 맞게 적절하게 지정해주시면 됩니다.

2. @Async 어노테이션을 비동기로 실행할 메서드에 붙여줍니다.

이때 주의할 점은 @Async 어노테이션이 붙은 메서드의 리턴타입은 void 혹은

    java.util.concurrent.Future타입이라는 것입니다.

다른 타입으로 지정시 무조건 null이 넘어오므로 주의해야 합니다.

@Async
public Future<Integer> test() {
    System.out.println("===========================");
    System.out.println("test메서드 실행###");
    System.out.println("===========================");

    return new AsyncResult<Integer>(-2);
}

마지막으로 간단한 테스트케이스를 작성해 보았습니다.

@Test
public void async_리턴값확인() throws InterruptedException, ExecutionException {
	assertThat(asyncTest.test().get(), is(-2));
}

해당 테스트를 실행하면, 에러없이 결과가 출력됨을 확인할 수 있습니다.

*참고 url

[Java]정규식 “|(or)” 사용하기

어제 정규식예제로 포스팅했던 [Java]정규식 예제 에서 마지막 예제였던

@Test
	public void 볼드_이탤릭체_함께치환() {
		String str = "이젠 나도 _지쳤어_ 그냥 *힘차게223dfefef* 발돋움 하는거야!!";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_");
		Pattern p1 = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher m = p.matcher(str);
		Matcher m1 = p1.matcher(str);

		while(m.find()) {
			str = str.replace(m.group(), getHtml(m.group(), EsCharacter.ITALIC));
		}

		while(m1.find()) {
			str = str.replace(m1.group(), getHtml(m1.group(), EsCharacter.BOLD));
		}

		logger.debug("## 볼드/이탤릭체 함께 치환한 문자열 : {}", str);
	}

위의 예제처럼, 여러개의 정규식을 별도로 선언하고 매칭해 치환하는 테스트케이스가

있었고, 수정해야될 여지가 있다고 끝맺음을 했습니다.

회사동료 정재훈씨의 도움으로 연산자 “|(or)”을 사용하여 간단히 처리하였습니다.

@Test
	public void 볼드_이탤릭체_함께치환2() {
		String str = "이젠 나도 _지쳤_어_ 그냥 *힘차게2**23dfefef* 발돋움 하는거야!!";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_|\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher m = p.matcher(str);
		String matchingStr  = null;

		while(m.find()) {
			logger.debug("## 일치하는 문자열 : {}", m.group());
			matchingStr = m.group();

			if(matchingStr.contains("_")) {
				str = str.replace(m.group(), getHtml(m.group(), EsCharacter.ITALIC));
			}

			if(matchingStr.contains("*")) {
				str = str.replace(m.group(), getHtml(m.group(), EsCharacter.BOLD));
			}
		}

		logger.debug("## 볼드/이탤릭체 함께 치환한 문자열2 : {}", str);
	}

	public String getHtml(String str, EsCharacter ch) {
		String result = null;

		switch (ch) {
		case BOLD:
			result = "<b>"+replaceChBlank(str,"\\*")+"</b>";
			break;
		case ITALIC:
			result = "<I>"+replaceChBlank(str,"\\_")+"</I>";
			break;
		default:
			break;
		}

		return result;
	}

	public String replaceChBlank(String str, String ch) {
		return str.replaceAll(ch, "");
	}

	public enum EsCharacter {
		BOLD, ITALIC;
	}

 

생각보다 간단하죠.

다만, 여러개의 정규식을 한번에 치환해야 할 경우에 과연 모든 것을 or로 연결해서

치환해야 하는지는 더 생각해봐야 할것 같습니다.

패턴에 대한 정규식을 따로 자료구조에 정의해서 compile 메서드의 인자로 넘기는

방법은 어떨까 싶네요.

결과적으로 똑같지만, 코드의 가독성은 훨씬 나아지지 않을까 싶습니다.

더 효율적인 해결방법을 알고 계시면 댓글 남겨주세요^^

[Java]정규식 예제

지금 오픈소스팀에서 만들고 있는 위키중 중요부분인 파서부분의 정규식 예제를 간단히

만들어보았습니다.

많은 위키구문중에 대표적으로 “볼드“와 “이탤릭체“만 테스트코드로

작성했습니다.

고민중인 것은, “볼드와 이탤릭체를 함께 치환할 수 없느냐 하는 문제“입니다.

아래 코드를 보시면,

package com.glider.test.service;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author bluepoet
 *
 */
public class GliderWikiTest {
	Logger logger = LoggerFactory.getLogger(GliderWikiTest.class);

	@Test
	public void 별표_가운데문자열_처리() {
		String str = "지금은 알 수 없어 *그대aff2*";
		Pattern pattern = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher match = pattern.matcher(str);

		for(int i=0; match.find(); i++) {
			logger.debug("## 정규식안의 문자열 빼내기 : {}", match.group(1));
		}
	}

	@Test
	public void 별표_볼드로_치환() {
		String str = "지금은 알 수 없어 *그대aff2* 떠나는 내 진심을*fefefefefefe럴마널더22323*";
		Pattern pattern = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher match = pattern.matcher(str);

		while(match.find()) {
			logger.debug("## 일치하는 문자열 : {}", match.group());
			str = str.replace(match.group(), getHtml(match.group(), EsCharacter.BOLD));
		}

		logger.debug("## 별표를 볼드로 치환한 문자열 : {}", str);
	}

	@Test
	public void 이탤릭체_치환() {
		String str = "이젠 나도 _지쳤어_";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_");
		Matcher match = p.matcher(str);

		while(match.find()) {
			logger.debug("## 일치하는 문자열 : {}", match.group());
			str = str.replace(match.group(), getHtml(match.group(), EsCharacter.ITALIC));
		}

		logger.debug("## 언더바를 이탤릭체로 치환한 문자열 : {}", str);
	}

	@Test
	public void 볼드_이탤릭체_함께치환() {
		String str = "이젠 나도 _지쳤어_ 그냥 *힘차게223dfefef* 발돋움 하는거야!!";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_");
		Pattern p1 = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher m = p.matcher(str);
		Matcher m1 = p1.matcher(str);

		while(m.find()) {
			str = str.replace(m.group(), getHtml(m.group(), EsCharacter.ITALIC));
		}

		while(m1.find()) {
			str = str.replace(m1.group(), getHtml(m1.group(), EsCharacter.BOLD));
		}

		logger.debug("## 볼드/이탤릭체 함께 치환한 문자열 : {}", str);
	}

	public String getHtml(String str, EsCharacter ch) {
		String result = null;

		switch (ch) {
		case BOLD:
			result = "<b>"+replaceChBlank(str,"\\*")+"</b>";
			break;
		case ITALIC:
			result = "<I>"+replaceChBlank(str,"\\_")+"</I>";
			break;
		default:
			break;
		}

		return result;
	}

	public String replaceChBlank(String str, String ch) {
		return str.replaceAll(ch, "");
	}

	public enum EsCharacter {
		BOLD, ITALIC;
	}
}

볼드_이탤릭체_함께치환() 메소드를 보면, 각자 정규표현식을 따로 만들고 매쳐도 따로

만들어 while문을 두번 돌리고 있습니다.

실제로 파서를 만들때, 당연히 저런 방식으로는 만들면 안되겠지요.

한번 전체 구문을 돌면서 모든 위키구문을 치환하는게 바람직할 것입니다.

 

jspwiki를 좀 뜯어봤는데, 파서 부분을 파악하는것도 쉽지가 않네요 ㅎㅎ

 

*테스트*입니다. 잘 _될까요_??

 

위의 예제 문자열에서 *와 _사이의 문자열을 정규식으로 동시에 치환할 수 있는

좋은 방법 알고 계시면 댓글 부탁드릴게요^^