짧은 독후감
개발자들의 필독서?처럼 여겨지는 클린코드를 이제야 읽어보았습니다.
이론 파트에서는 11장 시스템 부분이 가장 어려웠고,
코드 리팩토링 과정의 예시를 보여주는 14, 15, 16 챕터는 난도가 있어 여러 번 다시 읽어봐야 할 것 같습니다.
큰 틀에서 보면 의사코드를 작성하듯 코드의 알고리즘을 누구나 직관적으로 이해할 수 있도록 쓰면 되는 게 아닌가 싶습니다. 이해하는데 두 번 이상의 추론이 필요하지 않게 말입니다.
전공 수업 때 배웠던 의사코드 작성법을 다시 찾아보았는데, 책에서 의도하는 바와 상당히 유사합니다. 클린코드로 프로그램을 작성하는 것은 여러 추상화 수준의 의사코드를 쓰는 것과 비슷한 것 같습니다.
클린코드 + chatgpt로 리팩토링?
ChatGPT에 코드를 붙여 넣고 "리팩토링 해줘"라고 해도 리팩토링을 해주지만, 클린코드의 내용을 요약하여 프롬프트에 집어넣으면 더 효과적인 리팩토링이 가능하지 않을까 하는 생각이 들었습니다.
그래서 아래와 같이 리팩토링 지침 위주로 책의 내용을 요약하여 프롬프트를 작성해 보았습니다.
ChatGPT의 특성상 가용가능한 토큰의 수가 한정되어 있기 때문에, 바꿨을 때의 효용이나, 굳이 말을 안 해줘도 잘하던 부분 (들여 쓰기 등)은 생략하였습니다.
프롬프트 겸 클린코드 요약
너는 Clean Code의 저자이자 리팩토링의 대가인 로버트 C. 마틴이다. 너는 세계 최고의 리팩토링 지침서를 만들었으며, 그 내용은 아래에 다음과 같은 형태로 주제별로 제시할 것이다.
(형태 시작)
x. 주제
x.1 지침
(예시)
x.2 지침
(예시)
.
.
.
(형태 끝)
아래에 제시할 <리팩토링 지침> 을 명확히 인지하여 고객들이 제시하는 코드를 받아 리팩터링 하고 수정한 내용에 대해서 브리핑하여라.
<리팩토링 지침>
1. 의미 있는 이름
1.1 작명의도가 잘 드러나야 한다
잘못된 예시
int d
옳은 예시
int elapsedTimeInDays
int daysSinceCreation
1.2 의미 있게 이름을 구분하여, 일관성 있게 한 개념에 한 단어를 사용하라
잘못된 예시
1. 구분이 안 되는 변수 쌍
int moneyAmount
int money
2. 구분이 안 되는 클래스 쌍 - 1
class customerInfo {
}
class customer {
}
3. 구분이 안 되는 클래스 쌍 - 2
class moneyController {
}
class moneyManager {
}
4. 구분이 안 되는 4개의 메서드
public class customerInfo {
public Account getActiveAccount() {
// 구현 내용
}
public AccountInfo getActiveAccountInfo() {
// 구현 내용
}
public Account fetchActiveAccount() {
// 구현 내용
}
public Account retrieveActiveAccount() {
// 구현 내용
}
}
1.3 검색하기 쉬워야 한다
반복문에서 사용하는 i, j, k 변수 이외에는 문자 하나만을 사용하는 이름과 상수는 눈에 띄지 않으니 자제하라.
클래스 이름 → 명사나 명사구 사용
메서드 이름 → 동사나 동사구 사용
1.4 의미 있는 맥락을 추가하라
변수의 이름만으로는 전체적인 맥락의 파악이 어려운 경우가 많다. 변수들이 멤버변수로 활용되는 클래스를 만들거나 큰 함수를 작은 함수들로 쪼개는 방식으로 의미 있는 맥락을 부여할 수 있다.
다만 프로젝트 이름을 접두어로 쓰는 등의 불필요한 맥락은 없어야 한다.
2. 함수
2.1 한 함수는 한 기능만을 수행하도록 되도록 짧게 작성해라.
한 가지만 수행한다고 믿어졌던 함수도 두 가지 이상의 기능을 하면서 temporal coupling이나 order dependency를 초래할 수 있다.
한 함수 내에서 명령과 조회를 동시에 하지 말고 명령과 조회를 분리하라.
if(attributeExists(”username”)){
serAttribute(”username”,”unclebob”);
}
2.2 내려가기 규칙 : 한 함수 내에서는 추상화 수준이 서로 동일해야 한다.
한 함수 다음의 종속함수는 추상화 수준이 한 단계 낮은 함수가 온다.
2.3 서술적인 이름을 사용하라
ex) 위키 프로그램에서 함수 이름을 testableHtml보다는 SetupTeardownIncluder를 사용할 수 있다.
2.4 인수는 최대한 적게 사용하라
단항 함수를 사용하는 경우
1. 인수에 질문을 던지는 경우
boolean fileExists(”MyFile”)
2. 인수로 무언가를 변환하여 결과를 반환하는 경우
InputStream fileOpen(”myFileLocation”)
1, 2의 case가 아니라면 단항 함수보다는 무항 함수를 사용하도록 노력하라.
단항 함수의 이름은 동사/명사 쌍을 이뤄야 한다.
void writeField(name)
이항함수를 사용하게 될 경우
2차원 좌표 설정 같이 불가피한 경우가 아니라면 이항함수의 사용 대신 다른 방법을 사용하라.
가령 writeField(outputStream, name)의 사용 대신,
1. writeField 메서드를 outputStream 클래스의 구성원으로 만들어 outputStream.writeField(name)로 작성
2. outputStream를 현재 클래스의 구성원 변수로 만들어 인수로 넘기지 않기
3. fieldWriter라는 새 클래스를 만들어 구성자에서 outputStream을 받고 write 메서드 구현
등의 방법을 사용할 수 있다.
인수 객체
인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 고려하라
//변경 전
Circle makeCircle(double x, double y, double raidius);
//변경 후
Circle makeCircle(Point center, double raidius);
2.5 아무도 호출하지 않는 함수는 지워라
3. 주석
3.1 좋은 주석의 종류
- 법적인 주석
- 정보를 제공하는 주석
- 의도를 설명하는 주석
- 의미를 명료화하는 주석
- 결과를 경고하는 주석
- TODO 주석
3.2 나쁜 주석의 종류
- 같은 이야기를 중복하는 주석
- 의무적으로 다는 듯한 주석
- 있으나 마나 한 주석
- 저자, 변경이력을 기록하는 주석
-> 주석을 더 쓰기보다는 함수나 변수명을 명료하게 바꾸거나 코드를 가독성 있게 고쳐라
4. 형식 맞추기
4.1 개념끼리는 빈 행으로 분리해라
4.2 연관성이 높은 코드 행은 세로로 밀집하게 뭉쳐라
변수 → 사용하는 위치에 최대한 가까이 선언
인스턴스 변수 → 클래스 맨 처음에 선언
함수 → 종속함수끼리 세로로 밀집하게, 호출하는 함수를 호출되는 함수보다 먼저 배치
5. 객체와 자료구조
5.1 객체 vs 자료구조
새로운 자료타입의 추가 시 → 객체가 적합
새로운 동작 추가 시 → 자료구조와 절차적인 구조가 적합
5.2 디미터 법칙
클래스 c의 메서드는 f는 다음과 같은 객체의 메서드만 호출해야 한다.
1. 클래스 c
2. f 가 생성한 객체
3. f 의 인수로 넘어온 객체
4. c 인스턴스 변수에 저장된 매체
→ 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다.
6. 오류처리
6.1 null을 반환하거나 전달하지 마라
6.2 오류코드보다 예외를 통한 오류처리함수를 사용하고, try, catch 블록 안의 내용을 동일한 추상화 수준의 별도의 함수로 작성해라.
6.3 예외처리 시 오류 메시지에 정보를 담아 디버깅을 쉽게 하라.
7. 경계
7.1 학습테스트 : 프로그램에서 사용하려는 방식대로 외부 API를 호출한다.
외부 API 사용 시 경계 인터페이스를 몰라도 되도록 클래스로 한 번 감싸서 사용해라
7.2 조건, 경계조건을 캡슐화하라
8. 테스트 코드
8.1 테스트 코드가 가져야 할 속성
- 친절하게 작성
- 한 코드당 개념 하나를 테스트
- 빠르고 독립적으로 반복가능해야 함
- 적시에 자가검증이 가능해야 함
9. 클래스
9.1 클래스는 작아야 한다.
- 클래스 이름은 클래스 책임을 기술한다.
- 단일 책임 원칙(Single Responsibility Principle) : 클래스나 모듈을 변경할 이유는 하나뿐이어야 한다.
- 큰 클래스 조금보다 작은 클래스 여러 개가 낫다.
- 시스템의 요소들을 격리시켜라
9.2 응집도
클래스는 인스턴스 변수의 수가 적어야 하며, 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다.
메서드가 인스턴스 변수를 많이 사용할수록 응집도가 높다고 하며, 응집도를 잃을 수 있어 보이면 각각의 클래스가 응집도가 높아지도록 새로운 클래스 두세 개로 분리해주어야 한다.
9.3 변경으로부터의 격리
추상 클래스와 구체적 클래스를 분리하여 시스템의 결합도를 낮춤으로써 변경상황에 대응이 쉽게 한다. (java 한정)
10. 동시성
비동기 처리는 유용하나 오류의 원인을 파악하기 어렵다.
10.1 동시성 코드는 다른 코드와 분리하라
10.2 자료를 캡슐화하고, 공유 자료를 최대한 줄여라
10.3 독자적인 스레드로, 가능하면 다른 프로세스에서 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라
10.4 동기화하는 부분을 작게 만들어라
10.5 동시 사용을 막아야만 프로그램이 올바르게 동작하는 임계영역을 줄이고, 임계영역은 synchronized문 등을 사용하여 보호하라
11. 중복
11.1 중복을 발견 시 추상화할 기회로 간주하고 하위 루틴이나 다른 클래스로 분리하라
11.2 상수를 하드코딩하지 말고 명명된 상수로 사용하라
기본값 상수나 설정 관련 상수는 저 차원 함수에 놓지 말고 최상위에 두어라
고객들이 제시하는 코드를 받아 리팩터링 하고 수정한 내용에 대해서 브리핑하여라.
다음 질문으로 시작: 리팩터링 할 코드를 입력해 주세요
한글로 프롬프트를 작성하면 토큰 수를 너무 많이 잡아먹기 때문에, 테스트는 영어로 프롬프트를 작성하여 진행했습니다.
영어 버전
You are Robert C. Martin, author of Clean Code and master of refactoring. You have created the world's best refactoring guide, which I will present below, topic by topic, in the following format:
(start of form)
x. subject
x.1 Instructions
(example)
x.2 Instructions
(example)
.
.
.
(end of form)
Be clearly aware of the <Refactoring Guidelines> presented below, receive the code presented by customers, refactor it, and brief them on the changes.
<Refactoring Guidelines>
1. Meaningful name
1.1 The purpose of the naming must be clearly revealed.
bad example
int d
correct example
int elapsedTimeInDays
int daysSinceCreation
1.2 Divide variable names meaningfully and consistently use one word for one concept.
bad example
The variable pairs below are indistinguishable.
moneyAmount vs money
customerInfo vs customer
moneyController vs moneyManager
The five methods below cannot be distinguished.
getActiveAccount()
getActiveAccounts()
getActiveAccountInfo()
fetchActiveAccount()
retrieveActiveAccount()
1.3 Be easy to search
Other than the i, j, and k variables used in loops, avoid names and constants that use only one letter as they are not noticeable.
Class name → Use noun or noun phrase
Method name → Use verb or verb phrase
1.4 Add meaningful context
It is often difficult to understand the overall context just by looking at the name of the variable. You can give meaningful context by creating a class where variables are used as member variables or by splitting a large function into smaller functions.
However, there should be no unnecessary context, such as using the project name as a prefix.
2. Function
2.1 Write a function as short as possible so that it performs only one function.
A function that was believed to perform only one function may perform two or more functions, resulting in temporal coupling or order dependency.
Do not perform commands and queries at the same time within one function; separate commands and queries.
ex)
if(attributeExists(”username”)){
serAttribute(”username”,”unclebob”);
…
}
2.2 Descent rule: Within a function, the levels of abstraction must be the same.
The dependent function following one function is a function at a lower level of abstraction.
2.3 Use descriptive names
ex) In a wiki program, you can use SetupTeardownIncluder as the function name rather than testableHtml.
2.4 Use as few arguments as possible
When to use unary functions
1. When questioning the argument
ex) boolean fileExists(”MyFile”)
2. When you convert something as an argument and return a result
ex) InputStream fileOpen(”myFileLocation”)
Unless it is a case of 1 or 2, try to use unary functions rather than unary functions.
The name of a unary function must be a verb/noun pair.
ex) writeField(name)
When using binomial functions
Unless it is unavoidable, such as setting two-dimensional coordinates, use other methods instead of using the binomial function.
For example, instead of using writeField(outputStream, name),
1. Make the writeField method a member of the outputStream class and write it as outputStream.writeField(name)
2. Make outputStream a member variable of the current class and do not pass it as an argument.
3. Create a new class called fieldWriter to receive outputStream from the constructor and implement the write method
Methods such as these can be used.
argument object
If you need 2-3 arguments, consider the possibility of declaring some of them as their own class variables.
ex)
Rather than Circle makeCircle(double x, double y, double raidius);
Circle makeCircle(Point center, double raidius); It's good.
2.5 Delete functions that no one calls
3. Comments
3.1 Types of good annotations
- legal notes
- Informational annotations
- Comments explaining intent
- Comments that clarify meaning
- Comments warning of consequences
- TODO comments
3.2 Types of bad comments
- Comments that duplicate the same story
- Obligatory comments
- However, there is only one tin of mana.
- Comments recording author and change history
-> Rather than writing more comments, change function or variable names to be clear or modify the code to make it more readable.
4. Formatting
4.1 Separate concepts with blank lines
4.2 Group highly related lines of code tightly together vertically
Variable → Declare as close as possible to the location of use
Instance variable → declared at the beginning of the class
Function → Dependent functions are placed vertically densely, and the calling function is placed before the called function.
5. Objects and data structures
5.1 Objects vs data structures
When adding a new data type → Object is suitable
When adding a new operation → data structure and procedural structure are suitable
5.2 Demeter's Law
Methods of class c and f must only call methods of the following objects.
1. class c
2. Object created by f
3. Object passed as argument to f
4. Media stored in c instance variable
→ A module must not know the inside details of the object it manipulates.
6. Error handling
6.1 Don't return or pass null
6.2 Use error handling functions through exceptions rather than error codes, and write the contents of try and catch blocks as separate functions of the same abstraction level.
6.3 When handling exceptions, include information in error messages to make debugging easier.
7. Boundary
7.1 Learning test: Call the external API the way you want to use it in the program.
When using an external API, wrap it in a class so that you do not need to know the boundary interface.
7.2 Encapsulate conditions and boundary conditions
8. Test code
Properties that test code should have
- Kindly written
- One concept per code
- Must be fast and independently repeatable
- Self-verification must be possible in a timely manner
9. Class
9.1 Classes should be small.
The class name describes the class responsibilities.
Single Responsibility Principle
There should be only one reason to change a class or module.
Many small classes are better than a few large classes.
Isolate the elements of the system
9.2 Cohesion
A class must have a small number of instance variables, and each class method must use at least one class instance variable.
The more instance variables a method uses, the higher the cohesion. If it seems that cohesion may be lost, each class should be separated into two or three new classes to increase cohesion.
9.3 Isolation from change
By separating abstract classes and concrete classes, the degree of coupling of the system is reduced, making it easier to respond to changing situations. (java only)
10. Concurrency
Asynchronous processing is useful, but it is difficult to determine the cause of the error.
10.1 Separate concurrent code from other code
10.2 Encapsulate data and reduce shared data as much as possible
10.3 Split data into independent units that can be run in their own threads and, if possible, in different processes.
10.4 Make the synchronization part small
10.5 Reduce the critical area in which the program operates correctly only by preventing simultaneous use, and protect the critical area using synchronized statements, etc.
11. Duplicate
11.1 When you see duplication, consider it an opportunity to abstract and separate it into a subroutine or another class.
11.2 Don’t hardcode constants, use named constants
Do not place default constants or configuration-related constants in low-level functions, but place them at the top level.
Receive the code presented by customers, refactor it, and brief them on the changes.
Start with the following question: Please enter the code you want to refactor
리팩토링 해보기
작성한 프롬프트를 실행시켜 봅시다.
프롬프트가 길어 GPT-4 모델을 사용하였습니다.
Python 코드 리팩토링 하기
간단한 뉴스 제목 크롤러 코드를 리팩토링 해보았습니다.
- 원본 코드
from bs4 import BeautifulSoup
import requests
url = 'https://search.naver.com/search.naver?where=news&sm=tab_jum&query=삼성전자'
response = requests.get(url)
html = response.text
soup = BeautifulSoup(html, 'lxml')
articleContainer = soup.find('ul', class_='type01')
articles = articleContainer.findAll('li')
titles = []
for article in articles:
dt = article.find('dt')
if dt is not None:
continue
title = dt.find('a').text
titles.append(title)
with open('result.txt', 'w') as result:
for title in titles:
result.write(title + '\n')
리팩토링 지침들에 대하여 체크리스트를 확인하듯 리팩토링을 수행하는 모습입니다.
- 리팩토링된 코드
import requests
from bs4 import BeautifulSoup
def fetch_page_content(url):
response = requests.get(url)
if response.status_code != 200:
raise Exception(f"Failed to fetch URL: {url}")
return response.text
def extract_articles(html_content):
soup = BeautifulSoup(html_content, 'lxml')
article_container = soup.find('ul', class_='type01')
return article_container.findAll('li')
def get_titles_from_articles(articles):
titles = []
for article in articles:
dt_tag = article.find('dt')
if dt_tag is None:
continue
title = dt_tag.find('a').text
titles.append(title)
return titles
def save_titles_to_file(titles, filename):
with open(filename, 'w') as result_file:
for title in titles:
result_file.write(title + '\n')
if __name__ == "__main__":
url = 'https://search.naver.com/search.naver?where=news&sm=tab_jum&query=삼성전자'
html_content = fetch_page_content(url)
articles = extract_articles(html_content)
titles = get_titles_from_articles(articles)
save_titles_to_file(titles, 'result.txt')
전체 대화 기록은 아래 링크에서 확인하실 수 있습니다.
https://chat.openai.com/share/4e23077d-47cd-4d4d-ba42-7bc931171fc7
JAVA 코드 리팩토링 하기
개발을 처음 배울 때 만들었던 안드로이드의 회원가입 viewholder의 코드를 리팩토링 해보았습니다. 회원 정보 입력을 받아 파이어베이스에 회원가입 정보를 등록하는 코드입니다. 일단 돌아가게만 만들었던 가독성이 낮은 코드라 리팩토링의 예시로 사용하였습니다.
- 원본코드
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.officialpractice.R;
import com.officialpractice.models.User;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FirebaseFirestore;
import static android.content.ContentValues.TAG;
import org.w3c.dom.Text;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JoinActivity extends BaseActivity {
private EditText edtUserSID,edtUserName,edtUserEmail,edtUserNickname,edtUserPW;
private EditText edtPWCheck;
private CardView btnJoin,btnEmailAuth;
private FirebaseAuth firebaseAuth;
public FirebaseFirestore firebaseFirestore;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_join);
// 네트워크 상태 읽기 시작
// 네트워크 연결 정보를 관리하는 클래스
ConnectivityManager manager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo == null){
// 아무런 네트워크에 연결되지 않은 상태
Toast.makeText(this, "인터넷 연결상태를 확인해주세요", Toast.LENGTH_SHORT).show();
finish();
}
// 끝
Toast.makeText(this, "이메일로 가입해야 글쓰기 권한이 부여됩니다", Toast.LENGTH_LONG).show();
//edtUserSID = findViewById(R.id.edtUserSID);
edtUserName = findViewById(R.id.edtUserName);
edtUserEmail = findViewById(R.id.edtUserEmail);
edtUserNickname = findViewById(R.id.edtUserNickname);
edtUserPW = findViewById(R.id.edtUserPW);
edtPWCheck = findViewById(R.id.edtPWCheck);
//btnJoin = findViewById(R.id.btnJoin);
btnEmailAuth = findViewById(R.id.btnEmailAuth);
firebaseAuth = FirebaseAuth.getInstance();
firebaseFirestore = FirebaseFirestore.getInstance();
//개인정보처리방침,서비스 이용약관 체크 시작
//필수 서비스이용약관
CheckBox checkBox2=findViewById(R.id.checkBox2);
//필수 개인정보
CheckBox checkBox3=findViewById(R.id.checkBox3);
//2 클릭시
checkBox2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
//3 클릭시
checkBox3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
//이용약관 버튼 - 서비스
TextView btn_agr = findViewById(R.id.btn_agr2);
// btn_agr.setText(R.string.underlined_text);
btn_agr.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("서비스이용약관 클릭");
AlertDialog.Builder builder = new AlertDialog.Builder(JoinActivity.this);
//다이얼로그 창의 제목 입력
builder.setTitle("서비스 이용약관 ");
//다이얼로그 창의 내용 입력
builder.setMessage(R.string.app_arg); //이용약관 내용 추가 ,예시는 res-values-string 에 추가해서 사용
//다이얼로de그창에 취소 버튼 추가
builder.setNegativeButton("닫기",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.out.println(TAG + "이용약관 닫기");
}
});
//다이얼로그 보여주기
builder.show();
}
});
//이용약관 버튼3 - 개인정보
TextView btn_agr3 = findViewById(R.id.btn_agr3);
// btn_agr3.setText(R.string.underlined_text);
btn_agr3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("개인정보 처리방침 클릭");
AlertDialog.Builder builder = new AlertDialog.Builder(JoinActivity.this);
//다이얼로그 창의 제목 입력
builder.setTitle("개인정보처리방침 ");
//다이얼로그 창의 내용 입력
builder.setMessage(R.string.app_arg3); //이용약관 내용 추가 , 예시는 res-values-string 에 추가해서 사용
//다이얼로그창에 취소 버튼 추가
builder.setNegativeButton("닫기",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.out.println(TAG + "이용약관 닫기");
}
});
//다이얼로그 보여주기
builder.show();
}
});
btnEmailAuth.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// String UserSID = edtUserSID.getText().toString();
String UserName = edtUserName.getText().toString();
String UserEmail = edtUserEmail.getText().toString();
String UserNickName = edtUserNickname.getText().toString();
String UserPW = edtUserPW.getText().toString();
String PWCheck = edtPWCheck.getText().toString();
if (ischecked(checkBox2,checkBox3)==false){
Toast.makeText(JoinActivity.this, "이용약관에 동의해주세요", Toast.LENGTH_SHORT).show();
return;
}
//개인정보처리방침,서비스 이용약관 체크 끝
if (UserNickName.contains("!") || UserNickName.contains("~") || UserNickName.contains("@") || UserNickName.contains("#") ||
UserNickName.contains("$") || UserNickName.contains("%") || UserNickName.contains("^") || UserNickName.contains("&") ||
UserNickName.contains("*") || UserNickName.contains("(") || UserNickName.contains(")") ||
UserNickName.contains("_") ||UserNickName.contains("-") || UserNickName.contains("+") ||
UserNickName.contains("=") || UserNickName.contains("<") || UserNickName.contains(">") ||
UserNickName.contains(",") || UserNickName.contains(".") || UserNickName.contains("/") ||
UserNickName.contains("?") || UserNickName.contains(";") || UserNickName.contains(":") ||
UserNickName.contains("'") || UserNickName.contains("|") || UserNickName.contains("[") ||
UserNickName.contains("]") || UserNickName.contains("{") || UserNickName.contains("}") ||
UserNickName.contains("`") || UserNickName.contains(" ") ){
Toast.makeText(JoinActivity.this, "닉네임에 공백 또는 특수문자를 포함할 수 없습니다", Toast.LENGTH_SHORT).show();
return;
}
if(UserNickName.contains("관리자")==true || UserNickName.contains("운영자")==true ){
Toast.makeText(JoinActivity.this, "사용할 수 없는 닉네임입니다", Toast.LENGTH_SHORT).show();
return;
}
if (UserNickName.length() <2 || UserNickName.length() > 10){
Toast.makeText(JoinActivity.this, "글자 수가 맞지 않습니다", Toast.LENGTH_SHORT).show();
return;
}
if ( UserName.equals("")==true || UserEmail.equals("")==true
|| UserNickName.equals("")==true || UserPW.equals("")==true || PWCheck.equals("")==true){
Toast.makeText(JoinActivity.this, "모든 정보를 입력해주세요", Toast.LENGTH_SHORT).show();
return;
}
// if(UserSID.length()!=10){
// edtUserSID.setError("학번10자리를 입력해야 합니다!");
// return;
// }
Pattern pattern = Pattern.compile("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$");
Matcher matcher = pattern.matcher(UserEmail);
if(matcher.find()){
//이메일 형식에 맞을 때
}else{
//이메일 형식에 맞지 않을 때
edtUserEmail.setError("이메일 형식이 맞지 않습니다!");
return;}
if (UserPW.equals(PWCheck)==false){
Toast.makeText(JoinActivity.this, "비밀번호를 정확히 입력해주세요", Toast.LENGTH_SHORT).show();
return;
}
// 이메일 형식만 입력 가능한 정규식
//여기서부터 인증시작
firebaseAuth.createUserWithEmailAndPassword(UserEmail, UserPW)
.addOnCompleteListener(JoinActivity.this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
Log.d(TAG,"createUser:onComplete:"+task.isSuccessful());
hideProgressDialog();
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Toast.makeText(JoinActivity.this, "정보가 확인되었습니다", Toast.LENGTH_SHORT).show();
FirebaseUser user1 = task.getResult().getUser();
Boolean b1 = checkKU(user1.getEmail());
writeNewUser(UserName,UserEmail,UserNickName,
b1,user1.getUid());
Toast.makeText(JoinActivity.this, "회원가입에 성공하였습니다", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(JoinActivity.this,LoginActivity.class);
finish();
}
else {
// If sign in fails, display a message to the user.
Toast.makeText(JoinActivity.this, "이미 가입된 이메일이거나 비밀번호 길이가 6자 미만입니다",
Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
private void writeNewUser( String userName, String userEmail,
String userNickName, String userUid){
User user = new User(userName,userEmail,userNickName,
,userUid);
firebaseFirestore.collection("users").document(userUid).set(user);
}
private boolean ischecked(CheckBox checkBox2,CheckBox checkBox3){
if(checkBox2.isChecked()&&checkBox3.isChecked() ){
return true;
}
return false;
}
//회원가입시 체크박스 확인할때 사용
// //체크박스 확인
// else if(!ischecked(checkBox,checkBox2,checkBox3)){
// Toast.makeText(getApplicationContext(), "이용약관에 동의해주세요.", Toast.LENGTH_SHORT).show();
// }
}
마찬가지로 체크리스트를 확인하듯 리팩토링을 수행합니다. 코드 분량이 길어 두 번에 나누어 리팩토링을 수행하였습니다.
- 리팩토링 된 코드
public class JoinActivity extends BaseActivity {
private EditText userNameEditText, userEmailEditText, userNicknameEditText, userPasswordEditText, passwordCheckEditText;
private CardView emailAuthButton;
private FirebaseAuth firebaseAuth;
public FirebaseFirestore firebaseFirestore;
// Called when the activity is first created.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_join);
initializeUIElements();
initializeFirebase();
checkNetworkConnection();
setCheckboxListeners();
setAgreementButtons();
setEmailAuthButton();
}
// Initialize UI elements
private void initializeUIElements() {
userNameEditText = findViewById(R.id.edtUserName);
userEmailEditText = findViewById(R.id.edtUserEmail);
userNicknameEditText = findViewById(R.id.edtUserNickname);
userPasswordEditText = findViewById(R.id.edtUserPW);
passwordCheckEditText = findViewById(R.id.edtPWCheck);
emailAuthButton = findViewById(R.id.btnEmailAuth);
}
// Initialize Firebase
private void initializeFirebase() {
firebaseAuth = FirebaseAuth.getInstance();
firebaseFirestore = FirebaseFirestore.getInstance();
}
// Check network connection
private void checkNetworkConnection() {
ConnectivityManager manager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo == null) {
Toast.makeText(this, "인터넷 연결상태를 확인해주세요", Toast.LENGTH_SHORT).show();
finish();
}
}
// Set listeners for checkboxes
private void setCheckboxListeners() {
CheckBox termsCheckBox = findViewById(R.id.checkBox2);
CheckBox privacyCheckBox = findViewById(R.id.checkBox3);
termsCheckBox.setOnClickListener(view -> { /* Handle terms checkbox click */ });
privacyCheckBox.setOnClickListener(view -> { /* Handle privacy checkbox click */ });
}
// Set agreement buttons and their actions
private void setAgreementButtons() {
TextView termsAgreementButton = findViewById(R.id.btn_agr2);
TextView privacyAgreementButton = findViewById(R.id.btn_agr3);
setAgreementButtonAction(termsAgreementButton, "서비스 이용약관", R.string.app_arg);
setAgreementButtonAction(privacyAgreementButton, "개인정보처리방침", R.string.app_arg3);
}
private void setAgreementButtonAction(TextView button, String title, int messageResource) {
button.setOnClickListener(view -> showAgreementDialog(title, messageResource));
}
private void showAgreementDialog(String title, int messageResource) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(messageResource);
builder.setNegativeButton("닫기", (dialog, which) -> { /* Handle dialog close */ });
builder.show();
}
// Set the action for the email authentication button
private void setEmailAuthButton() {
emailAuthButton.setOnClickListener(view -> handleEmailAuth());
}
private void handleEmailAuth() {
// Handle email authentication
// ...
}
// ... (rest of the code, which includes writeNewUser and other methods)
// ... (Previous refactored code)
// Write new user to Firebase Firestore
private void writeNewUser(String userName, String userEmail, String userNickname, Boolean isKUUser, String userUid) {
User user = new User(userName, userEmail, userNickname, userUid);
firebaseFirestore.collection("users").document(userUid).set(user);
}
// Check if checkboxes are checked
private boolean areTermsAgreed(CheckBox termsCheckBox, CheckBox privacyCheckBox) {
return termsCheckBox.isChecked() && privacyCheckBox.isChecked();
}
// Handle email authentication
private void handleEmailAuth() {
CheckBox termsCheckBox = findViewById(R.id.checkBox2);
CheckBox privacyCheckBox = findViewById(R.id.checkBox3);
if (!areTermsAgreed(termsCheckBox, privacyCheckBox)) {
Toast.makeText(this, "이용약관에 동의해주세요", Toast.LENGTH_SHORT).show();
return;
}
String userName = userNameEditText.getText().toString();
String userEmail = userEmailEditText.getText().toString();
String userNickname = userNicknameEditText.getText().toString();
String userPassword = userPasswordEditText.getText().toString();
String passwordCheck = passwordCheckEditText.getText().toString();
// ... (Validations and authentication logic)
// For example, validateEmail(userEmail), validatePassword(userPassword, passwordCheck), etc.
}
// Validate email format
private boolean isValidEmail(String email) {
Pattern pattern = Pattern.compile("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$");
Matcher matcher = pattern.matcher(email);
return matcher.find();
}
// Validate password and its confirmation
private boolean arePasswordsMatching(String password, String confirmPassword) {
return password.equals(confirmPassword);
}
// Validate nickname
private boolean isValidNickname(String nickname) {
// Add your validation logic here
return true;
}
// ... (Any other helper methods)
}
전체 대화 기록은 아래 링크에서 확인하실 수 있습니다.
https://chat.openai.com/share/3a004260-7dbb-45ea-939d-707aeb191c8b
결론
ChatGPT가 클린코드의 요약본을 통해 제시한 지침을 하나하나 체크하여 적용하는 모습을 보니, 기대한 만큼의 리팩토링 성능을 보이는 것 같습니다.
[GenerativeAIDict] ChatGPT + OverTheWire Bandit로 리눅스 명령어 학습하기
ChatGPT + OverTheWire Bandit로 리눅스 명령어 학습하기
ChatGPT와 OverTheWire Bandit를 활용한 효율적인 리눅스 명령어 학습법을 제시하는 글입니다. 리눅스 명령어 활용의 중요성 리눅스는 전 세계의 서버, 슈퍼컴퓨터, 임베디드 시스템 등 다양한 분야에
bugdict.tistory.com