자바에서 람다 도입에 대한 평가를 부탁드립니다.

emptynote의 이미지

몇일전 동영상을 보게 되었는데 람다의 장점에 대한 내용이 있었습니다.

jdbc 전형적인 코드를 반복하여 코드에 넣지 않는 깔끔한 방법이였습니다.

개인적으로 람다에 대해서 거부감(?)이 있지만

반복적인 코드를 넣을 필요 없고 자원 해제에 대한 사용자 실수를 차단할 필요가 있다고 생각하여

제 나름대로 람다를 적용하여 변경을 해 보았습니다.

람다 도입후 가독성등 평가 부탁드립니다.

>> 변경후

public MessageResultRes doWork(String dbcpName, PasswordSearchReadyReq passwordSearchReadyReq) throws Exception {
		// FIXME!
		log.info(passwordSearchReadyReq.toString());
 
		try {
			ValueChecker.checkValidEmail(passwordSearchReadyReq.getEmail());
		} catch (RuntimeException e) {
			String errorMessage = e.getMessage();
			throw new ServerServiceException(errorMessage);
		}
 
		try {
			ValueChecker.checkValidIP(passwordSearchReadyReq.getIp());
		} catch (RuntimeException e) {
			String errorMessage = e.getMessage();
			throw new ServerServiceException(errorMessage);
		}
 
		MessageResultRes messageResultRes = new MessageResultRes();		
 
		/**
		 * '아이디 혹은 비밀번호 찾기' 서비스는 오직 일반 회원에 한에서만 서비스가 제공됩니다. 회원 테이블에는 손님, 관리자 그리고 일반회원
		 * 이렇게 3종류가 존재하는데 이중 2개 '아이디 혹은 비밀번호 찾기'서비스 대상자에서 제외합니다.
		 * 손님은 내부용 처리를 위해 존재할뿐 회원이 아니기때문에 제외하며 관리자는 보안상 허용해서는 안되기때문에 제외합니다.
		 */
 
		ServerDBUtil.execute(dbcpName, passwordSearchReadyReq, messageResultRes,
				(conn, create) -> {
			Record3<String, String, String> memberRecord = create
					.select(SB_MEMBER_TB.USER_ID, SB_MEMBER_TB.NICKNAME, SB_MEMBER_TB.EMAIL).from(SB_MEMBER_TB)
					.where(SB_MEMBER_TB.EMAIL.eq(passwordSearchReadyReq.getEmail()))
					.and(SB_MEMBER_TB.ROLE.eq(MemberRoleType.MEMBER.getValue())).forUpdate().fetchOne();
 
			if (null == memberRecord) {
				try {
					conn.rollback();
				} catch (Exception e1) {
					log.warn("fail to rollback");
				}
 
				String errorMessage = "입력한 이메일에 해당하는 일반 회원이 없습니다";
 
				throw new ServerServiceException(errorMessage);
			}
 
			String userID = memberRecord.get(SB_MEMBER_TB.USER_ID);
			String nickname = memberRecord.get(SB_MEMBER_TB.NICKNAME);
			String email = memberRecord.get(SB_MEMBER_TB.EMAIL);
 
			Record2<UByte, UByte> passwordSearchRequestRecord = create
					.select(SB_PWD_SERARCH_REQ_TB.FAIL_CNT, SB_PWD_SERARCH_REQ_TB.RETRY_CNT).from(SB_PWD_SERARCH_REQ_TB)
					.where(SB_PWD_SERARCH_REQ_TB.USER_ID.eq(userID)).fetchOne();
 
			if (null == passwordSearchRequestRecord) {
				byte[] secretAuthenticationValueBytes = new byte[8];
				SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
				random.nextBytes(secretAuthenticationValueBytes);
 
				String secretAuthenticationValue = CommonStaticUtil.Base64Encoder
						.encodeToString(secretAuthenticationValueBytes);
 
				Timestamp registeredDate = new java.sql.Timestamp(System.currentTimeMillis());
 
				create.insertInto(SB_PWD_SERARCH_REQ_TB).set(SB_PWD_SERARCH_REQ_TB.USER_ID, userID)
						.set(SB_PWD_SERARCH_REQ_TB.FAIL_CNT, UByte.valueOf(0))
						.set(SB_PWD_SERARCH_REQ_TB.RETRY_CNT, UByte.valueOf(1))
						.set(SB_PWD_SERARCH_REQ_TB.LAST_SECRET_AUTH_VALUE, secretAuthenticationValue)
						.set(SB_PWD_SERARCH_REQ_TB.LAST_REQ_DT, registeredDate).execute();
 
				EmilUtil.sendPasswordSearchEmail(userID, nickname, email, secretAuthenticationValue);
 
				conn.commit();
 
				ServerDBUtil.insertSiteLog(conn, create, log, userID, "아이디 혹은 비밀번호 찾기서비스 신청", registeredDate,
						passwordSearchReadyReq.getIp());
 
				conn.commit();
			} else {
				UByte failCount = passwordSearchRequestRecord.get(SB_PWD_SERARCH_REQ_TB.FAIL_CNT);
				UByte retryCount = passwordSearchRequestRecord.get(SB_PWD_SERARCH_REQ_TB.RETRY_CNT);
 
				if (ServerCommonStaticFinalVars.MAX_PASSWORD_WRONG_COUNT_OF_PASSWORD_SEARCH_SERVICE == failCount
						.shortValue()) {
					try {
						conn.rollback();
					} catch (Exception e1) {
						log.warn("fail to rollback");
					}
 
					String errorMessage = "아이디 혹은 비밀번호 찾기로 비밀번호 틀린 횟수가  최대 횟수 5회에 도달하여 더 이상 진행할 수 없습니다, 관리자에게 문의하여 주시기 바랍니다";
 
					throw new ServerServiceException(errorMessage);
				}
 
				if (ServerCommonStaticFinalVars.MAX_RETRY_COUNT_OF_PASSWORD_SEARCH_SERVICE == retryCount.shortValue()) {
					try {
						conn.rollback();
					} catch (Exception e1) {
						log.warn("fail to rollback");
					}
 
					String errorMessage = "아이디 혹은 비밀번호 찾기 재시도 횟수가 최대 횟수 5회에 도달하여 더 이상 진행할 수 없습니다, 관리자에게 문의하여 주시기 바랍니다";
 
					throw new ServerServiceException(errorMessage);
				}
 
				byte[] newSecretAuthenticationValueBytes = new byte[8];
				SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
				random.nextBytes(newSecretAuthenticationValueBytes);
 
				String secretAuthenticationValue = CommonStaticUtil.Base64Encoder
						.encodeToString(newSecretAuthenticationValueBytes);
				Timestamp registeredDate = new java.sql.Timestamp(System.currentTimeMillis());
 
				create.update(SB_PWD_SERARCH_REQ_TB)
						.set(SB_PWD_SERARCH_REQ_TB.RETRY_CNT, SB_PWD_SERARCH_REQ_TB.RETRY_CNT.add(1))
						.set(SB_PWD_SERARCH_REQ_TB.LAST_SECRET_AUTH_VALUE, secretAuthenticationValue)
						.set(SB_PWD_SERARCH_REQ_TB.LAST_REQ_DT, registeredDate)
						.where(SB_PWD_SERARCH_REQ_TB.USER_ID.eq(userID)).execute();
 
				EmilUtil.sendPasswordSearchEmail(userID, nickname, email, secretAuthenticationValue);
 
				conn.commit();
 
				String logText = new StringBuilder().append("아이디 혹은 비밀번호 찾기서비스 신청[재시도횟수=")
						.append(retryCount.shortValue() + 1).append("]").toString();
 
				ServerDBUtil.insertSiteLog(conn, create, log, userID, logText, registeredDate,
						passwordSearchReadyReq.getIp());
 
				conn.commit();
			}
 
			String successMessage = new StringBuilder().append("아이디 ").append(userID).append(" 이신 ").append(nickname)
					.append(" 님 비밀번호 찾기 준비 단계 처리가 완료되었습니다").toString();
			messageResultRes.setTaskMessageID(passwordSearchReadyReq.getMessageID());
			messageResultRes.setIsSuccess(true);
			messageResultRes.setResultMessage(successMessage);
		});
 
		return messageResultRes;
	}

>> 변경전

public MessageResultRes doWork(String dbcpName, PasswordSearchReadyReq passwordSearchReadyReq) throws Exception {
		// FIXME!
		log.info(passwordSearchReadyReq.toString());
 
		try {
			ValueChecker.checkValidEmail(passwordSearchReadyReq.getEmail());
		} catch (RuntimeException e) {
			String errorMessage = e.getMessage();
			throw new ServerServiceException(errorMessage);
		}
 
		try {
			ValueChecker.checkValidIP(passwordSearchReadyReq.getIp());
		} catch (RuntimeException e) {
			String errorMessage = e.getMessage();
			throw new ServerServiceException(errorMessage);
		}
 
		String successMessage = null;
 
 
 
		DataSource dataSource = DBCPManager.getInstance().getBasicDataSource(dbcpName);
 
		Connection conn = null;
		try {
			conn = dataSource.getConnection();
			conn.setAutoCommit(false);
 
			DSLContext create = DSL.using(conn, SQLDialect.MYSQL, ServerDBUtil.getDBCPSettings(dbcpName));
 
			/**
			 * '아이디 혹은 비밀번호 찾기' 서비스는 오직 일반 회원에 한에서만 서비스가 제공됩니다. 회원 테이블에는 손님, 관리자 그리고 일반회원
			 * 이렇게 3종류가 존재하는데, 손님은 내부용 처리를 위해 존재할뿐 회원이 아니기때문에 손님은 '아이디 혹은 비밀번호 찾기'서비스 대상자에서
			 * 제외되며 관리자는 보안상 허용해서는 안됩니다.
			 */
			Record3<String, String, String> memberRecord = create
					.select(SB_MEMBER_TB.USER_ID, SB_MEMBER_TB.NICKNAME, SB_MEMBER_TB.EMAIL).from(SB_MEMBER_TB)
					.where(SB_MEMBER_TB.EMAIL.eq(passwordSearchReadyReq.getEmail()))
					.and(SB_MEMBER_TB.ROLE.eq(MemberRoleType.MEMBER.getValue())).forUpdate().fetchOne();
 
			if (null == memberRecord) {
				try {
					conn.rollback();
				} catch (Exception e1) {
					log.warn("fail to rollback");
				}
 
				String errorMessage = "입력한 이메일에 해당하는 일반 회원이 없습니다";
 
				throw new ServerServiceException(errorMessage);
			}
 
			String userID = memberRecord.get(SB_MEMBER_TB.USER_ID);
			String nickname = memberRecord.get(SB_MEMBER_TB.NICKNAME);
			String email = memberRecord.get(SB_MEMBER_TB.EMAIL);
 
 
 
			Record2<UByte, UByte> passwordSearchRequestRecord = create
					.select(SB_PWD_SERARCH_REQ_TB.FAIL_CNT, SB_PWD_SERARCH_REQ_TB.RETRY_CNT).from(SB_PWD_SERARCH_REQ_TB)
					.where(SB_PWD_SERARCH_REQ_TB.USER_ID.eq(userID)).fetchOne();
 
			if (null == passwordSearchRequestRecord) {
				byte[] secretAuthenticationValueBytes = new byte[8];
				SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
				random.nextBytes(secretAuthenticationValueBytes);
 
				String secretAuthenticationValue = CommonStaticUtil.Base64Encoder
						.encodeToString(secretAuthenticationValueBytes);
 
				Timestamp registeredDate = new java.sql.Timestamp(System.currentTimeMillis());
 
				create.insertInto(SB_PWD_SERARCH_REQ_TB).set(SB_PWD_SERARCH_REQ_TB.USER_ID, userID)
						.set(SB_PWD_SERARCH_REQ_TB.FAIL_CNT, UByte.valueOf(0))
						.set(SB_PWD_SERARCH_REQ_TB.RETRY_CNT, UByte.valueOf(1))
						.set(SB_PWD_SERARCH_REQ_TB.LAST_SECRET_AUTH_VALUE, secretAuthenticationValue)
						.set(SB_PWD_SERARCH_REQ_TB.LAST_REQ_DT, registeredDate).execute();
 
				EmilUtil.sendPasswordSearchEmail(userID, nickname, email, secretAuthenticationValue);
 
				conn.commit();
 
				ServerDBUtil.insertSiteLog(conn, create, log, userID, "아이디 혹은 비밀번호 찾기서비스 신청", registeredDate,
						passwordSearchReadyReq.getIp());
 
				conn.commit();
			} else {
				UByte failCount = passwordSearchRequestRecord.get(SB_PWD_SERARCH_REQ_TB.FAIL_CNT);
				UByte retryCount = passwordSearchRequestRecord.get(SB_PWD_SERARCH_REQ_TB.RETRY_CNT);
 
				if (ServerCommonStaticFinalVars.MAX_PASSWORD_WRONG_COUNT_OF_PASSWORD_SEARCH_SERVICE == failCount
						.shortValue()) {
					try {
						conn.rollback();
					} catch (Exception e1) {
						log.warn("fail to rollback");
					}
 
					String errorMessage = "아이디 혹은 비밀번호 찾기로 비밀번호 틀린 횟수가  최대 횟수 5회에 도달하여 더 이상 진행할 수 없습니다, 관리자에게 문의하여 주시기 바랍니다";
 
					throw new ServerServiceException(errorMessage);
				}
 
				if (ServerCommonStaticFinalVars.MAX_RETRY_COUNT_OF_PASSWORD_SEARCH_SERVICE == retryCount.shortValue()) {
					try {
						conn.rollback();
					} catch (Exception e1) {
						log.warn("fail to rollback");
					}
 
					String errorMessage = "아이디 혹은 비밀번호 찾기 재시도 횟수가 최대 횟수 5회에 도달하여 더 이상 진행할 수 없습니다, 관리자에게 문의하여 주시기 바랍니다";
 
					throw new ServerServiceException(errorMessage);
				}
 
				byte[] newSecretAuthenticationValueBytes = new byte[8];
				SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
				random.nextBytes(newSecretAuthenticationValueBytes);
 
				String secretAuthenticationValue = CommonStaticUtil.Base64Encoder
						.encodeToString(newSecretAuthenticationValueBytes);
				Timestamp registeredDate = new java.sql.Timestamp(System.currentTimeMillis());
 
				create.update(SB_PWD_SERARCH_REQ_TB)
						.set(SB_PWD_SERARCH_REQ_TB.RETRY_CNT, SB_PWD_SERARCH_REQ_TB.RETRY_CNT.add(1))
						.set(SB_PWD_SERARCH_REQ_TB.LAST_SECRET_AUTH_VALUE, secretAuthenticationValue)
						.set(SB_PWD_SERARCH_REQ_TB.LAST_REQ_DT, registeredDate)
						.where(SB_PWD_SERARCH_REQ_TB.USER_ID.eq(userID)).execute();
 
				EmilUtil.sendPasswordSearchEmail(userID, nickname, email, secretAuthenticationValue);
 
				conn.commit();
 
				String logText = new StringBuilder().append("아이디 혹은 비밀번호 찾기서비스 신청[재시도횟수=")
						.append(retryCount.shortValue() + 1).append("]").toString();
 
				ServerDBUtil.insertSiteLog(conn, create, log, userID, logText, registeredDate,
						passwordSearchReadyReq.getIp());
 
				conn.commit();
 
				successMessage = new StringBuilder().append("아이디 ").append(userID).append(" 이신 ").append(nickname)
						.append(" 님 비밀번호 찾기 준비 단계 처리가 완료되었습니다").toString();
			}
 
		} catch (ServerServiceException e) {
			throw e;
		} catch (Exception e) {
			if (null != conn) {
				try {
					conn.rollback();
				} catch (Exception e1) {
					log.warn("fail to rollback");
				}
			}
 
			throw e;
		} finally {
			if (null != conn) {
				try {
					conn.close();
				} catch (Exception e) {
					log.warn("fail to close the db connection", e);
				}
			}
		}
 
		MessageResultRes messageResultRes = new MessageResultRes();
		messageResultRes.setTaskMessageID(passwordSearchReadyReq.getMessageID());
		messageResultRes.setIsSuccess(true);
		messageResultRes.setResultMessage(successMessage);
 
		return messageResultRes;
	}

>> 관련 인터페이스및 static 메소드

public interface DBExecutorIF {
	public void execute(Connection conn, DSLContext create) throws Exception;
}
 
public static void execute(String dbcpName, 
			DBExecutorIF dbExecutor) throws Exception {
		DataSource dataSource = DBCPManager.getInstance().getBasicDataSource(dbcpName);
 
		Connection conn = null;
		try {
			conn = dataSource.getConnection();
			conn.setAutoCommit(false);
 
			DSLContext create = DSL.using(conn, SQLDialect.MYSQL, ServerDBUtil.getDBCPSettings(dbcpName));
 
			dbExecutor.execute(conn, create); 
		} catch (ServerServiceException e) {
			throw e;
		} catch (Exception e) {
			if (null != conn) {
				try {
					conn.rollback();
				} catch (Exception e1) {
					InternalLogger log = InternalLoggerFactory.getInstance(ServerDBUtil.class);
					log.warn("fail to rollback");
				}
			}
 
			throw e;
		} finally {
			if (null != conn) {
				try {
					conn.close();
				} catch (Exception e) {
					InternalLogger log = InternalLoggerFactory.getInstance(ServerDBUtil.class);
					log.warn("fail to close the db connection", e);
				}
			}
		}
	}	
emptynote의 이미지

백종원의 골목식당에서 백종원 대표가 처음 음식 장사 하는 초보자들이 가장 답답한것이

자신이 바로 가고 있는건지 모르는것이 가장 불안하고 답답하다고 합니다.

어느 누구도 말해주고 바로 잡아 주는 사람이 없다.....

근데 이게 음식 장사 초보자만에게만 해당되는 말일까요.

오픈 소스로 개발을 하면서 혼자 북치고 장구 치면서 가장 어려운것은

내가 가는 방향이 맞는가? 라는 의문과 싸우는것입니다.

위에 람다도 마찬가지입니다.

람다를 도입하게 된 배경은 finally 로 감싼 자원 해제라는 반복적 코드의 제거입니다.

하지만 람다 도입에 따른 장점과 단점은 존재합니다.

람다 처음 쓰는 사람이 장점과 단점을 알리가 없습니다.

하지만 단점을 알지 못하고 "finally 로 감싼 자원 해제라는 반복적 코드의 제거" 라는 단순한 목적으로 선택을 했습니다.

정말로 람다 도입을 잘한걸까요?

아쉽게도 이 질문의 답은 람다를 사용해 본 경험이 있어야 답을 할 수 있습니다.

음식 장사 초보의 고독이 그러하듯이...

오픈 소스 개발도 일정 수준으로 올라설려면 고독이 필수인것 같습니다.