핌이의 일상

Programming/Python

좋은 모듈, 나쁜 모듈 (결합도와 응집도 / 시간적, 우연적 응집도의 차이)

핌이 (Pimgrim) 2024. 4. 8. 23:19

나쁜 모듈 : 결합도가 강함. 내부에 직접 영향

 

1. 내용 결합도 

class PaymentProcessor:
	def process_payment(self, amount):
    #여기서는 결제 처리에 관련된 코드가 포함되어 있음
    pass
    
class Order:
	def __init__(self, payment_processor
    
    def checkout(slef, amount):
    	#결제 프로세서의 메서드로 직접 호출하여 결합도가 높음
        self.payment_processor.process_payment(amount)

 

==> 만약 Payment Processor의 구현이 변경되면 Order 클래스도 변경해야 할 가능성이 높다. 

==> 해결 : 의존성 주입(Dependency Injection)과 같은 설계패턴을 사용하여 결합도를 낮추고 유연성을 높일 수 있다. 

 


2. 제어 결합도

 

==> 'AuthenticationManager' 클래스는 'User' 클래스에 직접 의존하고 있다. 

==> 'AuthenticationManager' 클래스는 'User' 클래스의 내부를 직접 제어하고 있고, 'User' 클래스의 객체를 직접 생성하고 있다. 

class User:
	def __init__(self, username, password):
    	self.username = username
        self.password = password
	
    def login(self):
    	#로그인 로직
        pass
        
class AuthenticationManager:
	def __init__(self):
    	self.user = User("admin", "password") #User 클래스에 직접 의존, 객체를 생성함
        
    def authenticate(slef, username, password):
    	if username == self.user.username and password == self.user.password:
        	self.user.login()
            return True
		else:
        	return False

 


3. 스템프 결합도

 

==> 인터페이스에 자료가 전달됨

class Order:
	def __init__(self, order_id, products):
    	self.order_id + order_id
        self.products = products
        
	def calculate_total(self):
    	#주문에 포함된 제품들의 가격을 합산하여 총 가격을 계산하는 로직
        total_Price = 0
        for product in self.products:
        	total_price += product['price']
            return total_Price
	
class Shipping:
	def __init__(self, order):
    	self.order = order

	def ship_order(self):
    	#주문 정보를 바탕으로 상품을 배송하는 로직
        for product in self.order.products:
        	print(f"Shipping {product['name']} to the customer...")

#예시에서 Order 클래스와 Shipping 클래스가 스탬프 결합도를 갖고 있음
#Order 클래스의 calculate_total 메서드에서 사용하는 products 변수와 Shipping 클래스의
#ship_order 메서드에서 사용하는 order 변수가 동일한 자료구조를 공유하고 있음

 

==> 위 코드에서 Order 클래스와 Shipping 클래스는 각각 주문과 배송에 관련된 서로 다른 기능을 담당하고 있다. 

==> 그러나 Shipping 클래스는 Order 클래스의 인스턴스를 받아서 주문정보를 활용하여 상품을 배송한다. 

==> 이로 인해, 두 클래스는 같은 자료 구조를 공유하고 있으며, 이것이 스탬프 결합도를 발생시키는 것이다.

 


 좋은 모듈

 

1. 기능적 응집도

 

==> 'Calculator' 클래스는 기본적인 산술 연산을 수행하는 기능을 가지고 있다. 

==> 모든 메서드는 숫자를 입력으로 받아서 산술 연산을 수행한다. 

==> 모든 메서드는 'Calculator' 클래스의 주요기능을 수행하는 데에 집중되어 있다. 

==> 이해와 수정이 쉽다. 재사용성이 높다. 

class Calculator:
	def add(self, a, b):
    	return a + b
	
    def subtract(self, a, b):
    	return a - b
        
	def multiply(self, a, b):
    	return a * b

	def devide(self, a, b):
    	if b == 0:
        	raise ValueError("Cannot devide by zero")
		return a / b

 


2. 순차적 응집도

 

==> 모듈 내 하나의 활동으로부터 나온 출력데이터를 그 다음 활동이 입력데이터로 사용할 경우

 

예시 1

def add_numbers(a, b):
	"""두 숫자를 더하는 함수"""
    return a + b

def set_user_input():
	"""사용자로부터 입력을 받는 함수"""
    num1 = float(input("첫 번째 숫자를 입력하세요: "))
    num2 = float(input("두 번째 숫자를 입력하세요: "))
    return num1, num2
    
def display_result(result):
	"""결과를 출력하는 함수"""
    print("결과는: ", result)
    
def main():
	"""주 실행 함수"""
    num1, num2 = get_user_input()
    result = add_numbers(num1, num2)
    display_result(result)
    
if __name__ == "__main__":
	main()

 

예시 2

def calculate_sum(numbers):
	"""리스트의 합을 계산하는 함수"""
    total = 0
    for num in numbers:
    	total += num
	return total
    
def main():
	"""주 실행 함수"""
    numbers = [1, 2, 3, 4, 5]
    total_sum = calculate_sum(numbers_
    print("리스트의 합: ", total_sum)
    
if __name__ == "__main__":
	main()

 


3. 통신적 응집도

 

==> 모듈내의 모든 요소가 공통된 목표를 위해 협력하는 것을 의미

def find_longest_word(sentence):
	"""주어진 문장에서 가장 긴 단어를 찾는 함수"""
    words = sentence.split() #문장을 단어로 분할
    longest_word = " "
    for word in words:
    	if len(word) > len(longest_word):
        	longest_word = word

	return longest_word
    
def main():
	"""주 실행 함수"""
    sentence = "Python programming is fun"
    longest = find_longest_word(sentence)
    print("가장 긴 단어: ", longest)
    
if __name__ == "__main__":
	main()

 


4. 절차적 응집도

 

==> 모듈이 특정기능을 수행하는데 집중되어 있으며, 다른 기능과 분리되어 있는 것을 나타냄

def find_even_numbers(numbers):
	"""주어진 숫자 리스트에서 짝수를 찾아 반환하는 함수"""
    evens = [num for num in numbers if num % 2 == 0]
    return evens
    
def find_odd_numbers(numbers):
	"""주어진 숫자 리스트에서 홀수를 찾아 반환하는 함수"""
    odds = [num for num in numbers if num % 2 != 0]
    return odds
    
def main():
	"""주 실행 함수"""
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    even_numbers = find_even_numbers(numbers)
    odd_numbers = find_odd_numbers(numbers)
    print("짝수: ", even_numbers)
    print("홀수: ", odd_numbers)
    
if __name__ == "__main__"
	main()

 


5. 시간적 응집도(Temporal Cohesion)

 

==> 동일한 시간에 실행되어야 하는 작업을 함께 묶는 것이 일반적

import time

def fetch_data():
    """데이터를 가져오는 작업"""
    print("데이터를 가져오는 중...")
    time.sleep(2)  # 2초 동안 대기
    print("데이터를 가져왔습니다.")

def process_data():
    """데이터를 처리하는 작업"""
    print("데이터를 처리하는 중...")
    time.sleep(3)  # 3초 동안 대기
    print("데이터 처리를 완료했습니다.")

def send_email():
    """이메일을 보내는 작업"""
    print("이메일을 보내는 중...")
    time.sleep(1)  # 1초 동안 대기
    print("이메일을 보냈습니다.")

def main():
    """주 실행 함수"""
    fetch_data()
    process_data()
    send_email()

if __name__ == "__main__":
    main()

 


6. 논리적 응집도

 

==> 단일 기능에 집중된 함수로 이루어짐

def find_min_max(numbers):
    """리스트에서 최솟값과 최댓값을 찾는 함수"""
    min_number = min(numbers)
    max_number = max(numbers)
    return min_number, max_number

def main():
    """주 실행 함수"""
    numbers = [5, 2, 8, 1, 9, 3, 6]
    min_number, max_number = find_min_max(numbers)
    print("최솟값:", min_number)
    print("최댓값:", max_number)

if __name__ == "__main__":
    main()

 


7. 우연적 응집도(Coincidental Cohesion)

 

==> 모듈내의 요소들이 서로 관련이 없는데도 함께 묶여있는 것이다. (여러가지 기능, 서로 다른 책임)

==> 가독성과 유지보수성을 저하시킨다. 코드의 재사용성이 떨어지고 버그를 찾고 수정하기가 어려워진다. 

==> 바람직한 방식으로 바꾸기 위해 코드의 구조를 개선해야 한다. (논리적 응집도가 높은 모듈로 재구성한다.)

import os

def find_min_value(numbers):
    """리스트에서 최솟값을 찾는 함수"""
    min_value = min(numbers)
    return min_value

def write_to_file(data):
    """데이터를 파일에 기록하는 함수"""
    file_path = "output.txt"
    with open(file_path, "w") as f:
        f.write(str(data))

def main():
    """주 실행 함수"""
    numbers = [5, 2, 8, 1, 9, 3, 6]
    min_value = find_min_value(numbers)
    write_to_file(min_value)
    print("최솟값을 파일에 기록했습니다.")

if __name__ == "__main__":
    main()

 

find_min_value(), write_to_fiile() 두 메서드는 서로 관련이 없다.

 


시간적 응집도와 우연적 응집도의 차이

 

시간적 응집도와 우연적 응집도는 모듈내의 요소들이 함께 묶이는 이유가 다르다.

 

▪ 시간적 응집도 : 모듈내의 요소들은 서로 연관된 작업을 동일한 시간에 실행하게 된다. (시간적 연관성)

▪ ▪ 예) 데이터를 가져오고 처리한 후에(징검다리 프로세스) 이를 이메일로 전송하는 것은 동일한 프로세스로 간주한다. 

 

▪ 우연적 응집도 : 예를 들어, 데이터를 가져오는 작업과 이메일을 보내는 작업이 함께 묶여있는 경우는 각 작업이 서로 다른 기능을 수행하며, 이는 좋지 않은 프로그래밍으로 간주된다. 

 


 

* 시간적 응집도가 높은 경우에는 모듈간의 결합도가 높아질 수 있지만, 의존성이 높은 기능들을 함께 관리한다는 것은 코드의 일관성과 연결된다. 

* 우연적 응집도의 각 기능들이 서로 의존성이 낮은 점은 독립성이 높은 것이긴 하지만, 코드의 품질을 떨어트리는 요인에는 여러가지가 있다. 

1. 의도가 명확히 전달되지 못한다. 

2. 코드의 가독성이 저하된다. 

3. 유지보수가 어렵다. 

4. 재사용성이 저하된다. 

 


프로세스 간의 연결 여부

▣ 결 론 ▣

"프로세스 간의 연결 여부"가 시간적 응집도와 우연적 응집도를 구별하는 큰 요소이다. 

 

 

반응형

'Programming > Python' 카테고리의 다른 글

Python | 1. 번호 입력받고 인덱싱 값 출력하기  (0) 2024.02.26
Random Library and Methods  (0) 2023.12.01