[WatchaPedia Clone] Day 7. 코드 리팩토링

jaylight·2020년 12월 24일
0

WatchaPediaClone

목록 보기
6/7

추가적인 기능구현에서 collection 기능을 빠르게 손절하고 프론트 분들과 진도를 맞추면서 가독성이 좋지 않거나 비효율적인 코드에 대해 받은 코드리뷰 사항을 반영하면서 리팩토링을 시작했다. 기존에 만들었더 기능 중 다른 API에서도 반복적으로 활용할만한 연산에 대해 재활용이 가능하도록 별도로 함수화하거나, 무의미하게 긴 코드를 줄이거나 각종 convention을 지키도록 수정하는 등의 작업을 수행했다.

convention

  • related_name은 복수형으로
class Archive(models.Model):
	user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='archived_movie')

기존 Foreign Key로 참조를 받으면서 위와 같은 형태로 related_name을 지정했으나, 위와 같이 작성했을 경우, views.py에서 사용했을 경우 foreign key와 구분이 가지 않게 되어서 구분하기 쉽게 복수형으로 작성하는 것이 가독성을 위에 좋다. 따라서 아래와 같은 형태로 수정하였다.

class Archive(models.Model):
	user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='archived_movies')
  • 함수명은 동사형을 시작으로

각 영화마다 평균 별점을 계산하는 기능이 여러 API에서 활용되면서, archive/utils.py 내에 함수로 별도 구성하도록 만들었다.

이 과정에서 처음 함수명을 ratings_calc로 지어었는데 이보다 어떤 기능을 동작, 수행 한다는 의미를 좀 더 강조하기 위해 함수명을 calculate_ratings로 수정하였다.

list comprehension 활용

results = []

for rating in ratings:
	results.append(
		{
			"id"      : rating.id,
			"user_id" : rating.user_id,
			"user"    : rating.user.username,
			"content" : rating.content.title_korean,
			"rating"  : rating.rating
		}
	)

위와 같이 여러 데이터를 읽어와서 테이블에 넣어 전송하는 데이터를 작성할 때, 리스트를 선언 후, 별도의 for문으로 데이터를 테이블에 넣기보다 list comprehension을 활용하여 좀 직관적이고 성능이 좋은 코드를 작성할 수 있다.

results = [{
	"id"      : rating.id,
	"user_id" : rating.user_id,
	"user"    : rating.user.username,
	"content" : rating.content.title_korean,
	"rating"  : rating.rating
} for rating in ratings]

정참조 & 역참조로 호출하기

기존에 특정 데이터 항목을 get 으로 변수에 할당해두고 해당 변수에 관련된 외부 테이블의 항목에 접근한다면, 해당 테이블에 또다시 조건을 걸어서 새로 검색하기보다 기존 변수가 참조 혹은 역참조로 연결되어 있는 점을 활용할 수 있다.

이에 대해서는 위에서 언급했던 related_name을 포함하여 언급할 개념이 많아서 별도로 빼두고 기존 코드에서 정참조, 역참조 호출 방식으로 리팩토링한 코드를 소개하고 넘어간다.

"user"    : User.objects.get(id=rating.user_id).username,
"content" : Content.objects.get(id=rating.content_id).title_korean,

기존 위와 같은 방식으로 각 rating 항목이 참조하는 user와 content 정보를 불러와서 다시 데이터를 꺼내오는 방식을 활용했었는데, 위와 같은 경우 rating이 이미 user와 content를 각각 정참조하고 있는 상황으로 아래과 같이 코드를 수정하여 좀 더 깔끔하게 작성할 수 있다.

"user"    : rating.user.username
"content" : rating.content.title_korean

auto_now & auto_now_add

기존 코드에서는 데이터가 생성된 시간(created_at)과 수정된 시간(updated_at)을 표시하기 위해 각각 timezone.now() 함수를 활용했다. 하지만 아래와 같이 models.py에서 수정한다면 조금 더 쉽게 생성 및 수정 시간을 표시할 수 있다.

created_at  = models.DateTimeField(auto_now_add=True)
updated_at  = models.DateTimeField(auto_now=True)

get_or_create

if Review.objects.filter(user = user, content = content).exists():
return JsonResponse({"message": "ALREADY_EXIST"}, status = 400)

Review.objects.create(user = user, content = content, body = body)
return JsonResponse({"message": "SUCCESS"}, status = 201)

새로 데이터를 생성하면서 해당 데이터가 기존에 없다면 생성하고, 있다면 오류메시지를 보내도록 구현하는 과정이 있었는데 이 과정에서 매번 if 문을 활용해 데이터 존재 유무를 확인하도록 구현했었다. 하지만 이 방법이 아닌 Django queryset API 중 get_or_create를 활용할 수 있다.

review, created = Review.objects.get_or_create(user    = user, 
                                               content = content)
if created:
	review.body = data['body']
	review.save()
	return JsonResponse({"message": "SUCCESS"}, status = 201)
return JsonResponse({"message": "ALREADY_EXIST"}, status = 400)

위 메서드를 활용하면 (object, created) 형태의 튜플이 반환된다. 첫 번째 object는 get을 통해 얻어오거나 기존 존재하는 값이 없어서 새로 생성하는 객체이고, created는 위 메서드를 통해 새로운 객체가 생성되었는지 여부를 boolean 형태로 보여준다. (True: object에서 값을 얻어오지 못해 새롭게 생성함 // False: 기존 object에서 값을 얻어옴) 따라서 위 코드는 created에서 새로운 값이 생성 시 데이터를 저장하고 성공 메세지를 보내고, 그렇지 않을 경우 "ALREAD_EXIST" 메세지를 보내도록 구성되어 있다.

0개의 댓글