ID:
PW:

     0 분
     10 분

엔지니어 돌종
  dolljong
프로그래밍 Tips
프로그래밍에 관한 Tip을 공유하는 곳입니다.


[python] (엔지니어를 위한) 파이썬 시작하기[9] 리팩터링
이석종  2022-09-08 03:56:11, 조회 : 281, 추천 : 60
- Download #1 : engpython09.png (46.8 KB), Download : 14

- Download #2 : sec.zip (27.4 KB), Download : 19

철근량 구하기 프로그램 1차 리팩터링(refactoring)

 

코딩에서 리팩터링이란 결과의 변경없이 구조를 재조정하는 것이라고 한다. 주로 가독성을 늘리거나 유지보수성을 좋게 하려고 한다. 오늘은 ESC그룹 멤버인 한종 이기동 상무님이 그동안 공부한 기능들을 이용해서 철근량 구하는 프로그램을 만들어서 단톡방에 공유해주셨다.

 

이 프로그램의 구조를 바꾸는 과정을 소개하고자 한다. 코드는 첨부파일로 올리도록 하겠다.

 

일단 이 프로그램의 기능은 이렇다. 일반적으로 엔지니어들이 많이 하는 업무 중 하나다.

 

1. 철근량을 구할 단면의 콘크리트,철근,폭, 높이 등의 input 내용이 들어 있는 파일을 읽는다.

2. 파일로부터 읽은 정보를 이용해서 철근량검토를 한다.

3. 계산된 결과를 파일로 출력한다. 

 

ESC멤버가 제공한 프로그램

sec_ver1.0.py : 화면 input

sec_ver1.0.1.py : 파일 input (숫자만 입력하는 방식)

sec_ver02.py : 파일 input format변경(fck=40 방식)

—-

sec_ver02.2.py : 필자가 1차 리팩토링한 프로그램

 

1. 초기버전 sec_ver1.0.py

아래는 초기버전 내용 일부다. 이 프로그램은 input()함수를 이용해서 화면에서 입력을 받는 방식이다.

 

f=open("d:/python/section_check.txt", 'w')            #생성파일 저장 디렉토리

f.write(
'◈ 직사각형 보\n\n')                           #메모장 출력
f.write(
'   ▷ 검 토 조 건\n\n')                        #메모장 출력  

fck=float(input(
"콘크리트 재료강도(fck≤40MPa)):"))              #재료강도 입력
fy=float(input(
"철근의 항복강도(fy):"))                  #철근항보강도 입력
Øc=float(input(
"콘크리트의 재료저항계수(Øc=0.65):"))   #콘크리트 저항계수 입력
Øs=float(input(
"철근의 재료저항계수(Øs=0.90):"))
f.write(
f'     재 료  강 도 : fck = {fck:2.1f} MPa,     fy = {fy:2.1f} MPa\n     재료저항계수 : Øc = {Øc:2.3f},       Øs = {Øs:2.3f}\n'#메모장 출력

 

검토 : 

1. 첫번째 줄에서 출력할 파일을 오픈했다. 그리고 문서의 제목 등을 f.write()함수로 파일에 출력했다. 그리고 input()함수로 재료강도를 입력받았다. 파일오픈→파일출력→화면입력 이런 순서로 첫 몇줄이 진행되었다. 출력과 입력이 섞여 있으면 읽기가 어려워진다. 그래서 보통 입력 → 계산 → 출력 순서로 프로그램을 만들면 읽기도 좋고 같은 기능끼리 모여있어서 찾기와 수정도 용이하다.

 

2. 출력파일의 디렉토리와 파일명이 고정되어있다. d:\python\section_check.txt. 출력파일의 위치를 입력받는다면 사용하기가 훨씬 쉬워질 것이다. 이부분에 대해서는 나중에 별도로 설명하겠다. 이번 수정에서는 그냥 디렉터리명을 뺀 파일 이름만 입력하는 것으로 수정했다. 그러면 현재 파이썬 스크립트 파일이 있는 디렉토리에 section_check.txt파일이 생성된다.

 

f=open("section_check.txt", 'w')

 

출력은 가능하다면 제일 마지막에!  입력 → 계산 → 출력 순서를 지키자.

 

수정: write()문을 모두 뒤쪽으로 이동해서 모아놓았다. 파일 오픈은 첫 write()문 바로 전에 써준다. 마지막 write()문뒤에 close()문을 써준다. 이것이 수정 내용의 전부다.

 

f=open("d:/python/section_check.txt", 'w')           #d드라이브 python폴더의 section_check.txt를 출력함
f.write(
'◈ 직사각형 보\n\n')                          #메모장 출력
f.write(
'   ▷ 검 토 조 건\n\n')                       #메모장 출력  
f.write(
f'     재 료  강 도 : fck = {fck:2.1f} MPa,     fy = {fy:2.1f} MPa\n     재료저항계수 : Øc = {Øc:2.3f},       Øs = {Øs:2.3f}\n'#메모장 출력

 

 

2. 두번째버전 sec_ver1.0.1.py

이 버전이 이전 버전과 다른점은 이전 버전은 입력을 화면에서 input()함수로 받다보니 실수로 잘 못입력하면 수정하려면 처음부터 다시 입력을 진행해야 되는 불편함이 있었다. 그래서 보통 데이터파일을 이용한다. 그래서 입력데이터를 텍스트파일로 만드는 방식으로 바꿨다.

 

코드의 앞부분과 input파일을 보자. input 파일은 첫줄은 설명 줄이고 두번째 줄부터 각각 fck, fy … 으로 구성되어 있다.

f=open("d:/python/sec_input.txt", 'r') #d드라이브 python폴더의 sec_input.txt을 읽어드림
line=f.readlines()
f.close()

sentence=[]                       
#리스트를 초기화 하여 sentence에 지정
for iresult in line[1:]:           #모든줄의 리스트 요소[0번째,1번째,2번째....]중 1번째부터 세어서 개체수 만큼 반복
  isplt=iresult.split()           
#isplt의 요소를 순차적으로 문자열에서 공백을 기준으로 리스트를 분할
  sentence.append(isplt)           
#리스트 속의 리스트 작성[[],[],[]...](append함수 이용)
senlist=sentence                  
#senlist에 하나의 리스트로 변수지정
fck=int((senlist[
0][0]))           #fck설정
fy=int((senlist[
1][0]))            #fy설정
Øc=float(senlist[
2][0])            #Øc설정

 

1줄:fck, 2줄:fy, 3줄:Øc, 4줄:Øs, 5줄:Mu, 6줄:Vu, 7줄:H(단면두께), 8줄:B(단면폭), 9줄:Dc(피복두께), 10줄:AsDia(철근직경), 11줄:AsNum(철근개수), 12줄:δ(재분배 모멘트율),
30
400
0.65
0.9
620
450
1000
400
100
29
4
1.0

 

검토 :

1. 파일을 open하고 readlines()함수를 이용하여 파일 전체를 읽었다. 그리고 close를 했다. 이렇게 한꺼번에 읽는 것이 중간에 다른 코드가 있는 것보다 에러가 발생할 확률이 적다. 추천!

open문의 파일명은 디렉토리명을 삭제하고 파일명만 남겨놨다. 파이썬 스크립트가 있는 디렉토리에서 sec_input.txt를 찾게 될 것이다.

 

2. for문을 사용해서 두번째줄부터 마지막 줄까지 처리를 했다. 슬라이싱 [1:]을 이용했다. 인풋파일의 첫번째 줄은 설명이므로 건너뛴 것.

 

3. for문 안에서 split함수를 썼다. split함수는 공백이나 , 등으로 구분된 텍스트를 나눌 때 쓰는 함수다. 이런 경우 한줄에 데이터가 하나이기 때문에 split함수를 필요 없다. 사용자가 실수로 30의 앞이나 뒤에 공백을 입력했다고 하더라도 int나 float함수는 에러 없이 작동한다.  split함수를 쓰지 않으면 senlist[0][0]에서 뒷부분은 필요 없다. 뒷부분[0]은 split한 결과의 맨 앞의 것이라는 뜻인데 split을 안했으니 필요가 없다. 이렇게 바꿔도 결과는 같다. append문에 split결과가 들어가는 isplt 대신 iresult가 들어갔다는 것을 주목하자.

 

for iresult in line[1:]:           #모든줄의 리스트 요소[0번째,1번째,2번째....]중 1번째부터 세어서 개체수 만큼 반복
 
#isplt=iresult.split()            #isplt의 요소를 순차적으로 문자열에서 공백을 기준으로 리스트를 분할
 
#sentence.append(isplt)           #리스트 속의 리스트 작성[[],[],[]...](append함수 이용)
  sentence.append(iresult)
senlist=sentence                  
#senlist에 하나의 리스트로 변수지정
fck=int(senlist[
0])           #fck설정
fy=int(senlist[
1])            #fy설정
Øc=float(senlist[
2])            #Øc설정

 

3. 세번째 버전 sec_ver02.py

 

세번째 버전은 두번째 버전에서 input 파일의 포맷을 변경한 것이다. 즉 두번째 버전에서는 숫자만 나열했는데 세번째 버전에서는 fck=40과 같은 식으로 숫자 앞에 의미를 알 수 있는 텍스트를 추가한 것이다.

input파일을 보자.

입력은 등호(=)기준 한칸이상 띄우고 숫자입력(숫자만 입력) ex) fck(Mpa)= 30(O.K), fck(MPa)=   30(O.K), fck(MPa)=30(N.G)
※입력포멧은 변경하지 마십시요.!!(오류발생)
fck(MPa)=      
30
fy(MPa)=       
400
Øc=           
0.65

...

 

sentence=[]                        #리스트를 초기화 하여 sentence에 지정
for iresult in line[2:]:           #모든줄의 리스트 요소[0번째,1번째,2번째....]중 1번째부터 세어서 개체수 만큼 반복 (즉 0이면 공백줄이 없고, 1이면 첫번째줄까지 공백을 2이면 두번째줄까지 공백 의미)
  isplt=iresult.split()           
#isplt의 요소를 순차적으로 문자열에서 공백을 기준으로 리스트를 분할
  sentence.append(isplt)          
#리스트 속의 리스트 작성[[],[],[]...](append함수 이용)
senlist=sentence                  
#senlist에 하나의 리스트로 변수지정
fck=int((senlist[
0][1]))           #fck설정
fy=int((senlist[
1][1]))            #fy설정
Øc=float(senlist[
2][1])            #Øc설정

 

검토 :

1. 두번째 버전에서는 입력 포맷에 숫자만 있었기 때문에 split함수를 쓸 필요가 없어서 삭제했었다. 하지만 이번 버전처럼 fck(MPa)= 30와 같은 포맷을 쓴다면 split함수가 필요하다.  그런데 입력 포맷의 앞부분(fck=)은 필요가 없기 때문에 split의 결과를 별도로 저장할 필요는 없다. 이런 경우에는 string.split()[1] 이렇게 하면 split() 결과 리스트의 두번째 요소인 입력값(실제로는 문자열)을 취할 수 있다. 이렇게 하면 각 입력값을 변수에 할당하는 부분을 fck=int((senlist[0][1]))에서 fck=int(senlist[1])로 변경 가능하다.

for iresult in line[1:]:           #모든줄의 리스트 요소[0번째,1번째,2번째....]중 1번째부터 세어서 개체수 만큼 반복
  isplt=iresult.split()[
1]            #isplt의 요소를 순차적으로 문자열에서 공백을 기준으로 리스트를 분할
  sentence.append(isplt)          
#리스트 속의 리스트 작성[[],[],[]...](append함수 이용)
  sentence.append(iresult)
senlist=sentence                  
#senlist에 하나의 리스트로 변수지정
fck=int(senlist[
0])           #fck설정
fy=int(senlist[
1])            #fy설정
Øc=float(senlist[
2])            #Øc설정

 

2. 첫번째 버전에서 write()문을 한군데로 모았었다. write()함수를 여러번 쓸 필요 없이 출력할 내용을 하나의 문자열로 만들면 관리하기가 쉬워진다.

 

여러줄 문자열 지정하기 ”””   ”””

문자열을 정의하는 방법은 “나 ‘로 감싸는 것이다. ”””세개를 써서 감싸면 여러줄에 걸쳐 있는 문자열을 지정할 수 있다.

 

outputstr=f"""
◈ 직사각형 보\n\n
   ▷ 검 토 조 건\n\n 
     재 료  강 도 : fck = {fck:
2.1f} MPa,     fy = {fy:2.1f} MPa\n     재료저항계수 : Øc = {Øc:2.3f},       Øs = {Øs:2.3f}\n

 

. . . <중간생략>

 

Vcd.min = (0.4 Øc·fctk + 0.15 fn) B·D / 1000 = {Vcdmin:2.3f}kN\n"""

 

outputstr이라는 변수에 출력할 내용을 ””” 로 감싸고 f-string 포맷팅을 적용하였다. 기존의 write()함수 안에 썼던 내용 그대로다. 단지 ”””를 사용하면 write를 여러번 쓸 필요 없고 출력 양식을 하나의 문자열로 관리할 수 있다는 장점이 있다.

 

3. 하나의 문자열로 출력양식을 만드는데 있어서 해결해야 할 문제가 있따. if문 안에 들어 있는 write문 처리다. 세번째 버전에는 아래와 같이 if안에 write이 들어 있는 부분이 있다. 전단철근이 필요할 때와 필요하지 않을 때 각각 출력되는 내용이 달라야 한다.

if Vcd < Vu:
    sh3=
"... ∴전단철근 필요."
    f.write(
f'      ∴ Vcd = Max(Vc, Vcd.min) = {Vcd:2.3f}kN {sh2:2s} = {Vu:2.3f}kN {sh3:2s}\n\n')   #메모장 출력
    f.write(
f'      사용 전단철근량 Av.use = H{AvDia:2d} x {AvLeg:2.3f}ea = {Avs:2.3f}㎟  (간격 s = {AvSpace:2d}mm)\n')   #전단철근량 산정 출력
    f.write(
f'       z  = 0.9 D = {0.9*D:2.3f}mm\n')   #단면내부 팔길이 출력
   
    . . . <중간생략>
 
else:
    sh3=
"... ∴전단철근 불필요."   
    f.write(
f'      ∴ Vcd = Max(Vc, Vcd.min) = {Vcd:2.3f}kN {sh2:2s} = {Vu:2.3f}kN {sh3:2s}\n')   #메모장 출력

 

이런 경우 출력해야 할 내용을 별도의 문자열로 만들어서 최종 출력문자열에 붙이면 된다. shearchk라는 문자열에 전단철근이 필요할 때와 필요 없을 때의 출력 내용을 넣었다.

if Vcd < Vu:
    sh3=
"... ∴전단철근 필요."
    shearchk=
f'''      ∴ Vcd = Max(Vc, Vcd.min) = {Vcd:2.3f}kN {sh2:2s} = {Vu:2.3f}kN {sh3:2s}\n\n
      사용 전단철근량 Av.use = H{AvDia:
2d} x {AvLeg:2.3f}ea = {Avs:2.3f}㎟  (간격 s = {AvSpace:2d}mm)\n
       z  = 0.9 D = {
0.9*D:2.3f}mm\n'''   #단면내부 팔길이 출력

. . . <중간생략>


else:
    sh3=
"... ∴전단철근 불필요."   
    shearchk=
f'      ∴ Vcd = Max(Vc, Vcd.min) = {Vcd:2.3f}kN {sh2:2s} = {Vu:2.3f}kN {sh3:2s}\n'   #메모장 출력

 

이제 outputstr의 마지막에 shearchk를 더하면(연결) 된다. 그리고 write문에는 outputstr만 출력하고 파일을 close하면 된다.

 

outputstr=outputstr+shearchk

f.write(outputstr)

f.close()

 

4. 1차 리팩터링을 마치며

[ESC]그룹 단톡방에 필자가 올린 ‘필요한 기능’ 리스트의 맨 처음에 등장한 기능이 철근콘크리트 단면 검토하는 기능이었다. 며칠만에 두분이 코드를 올려주셨다. 태조의 설종명 부사장님, 한종의 이기동 상무님. 필자가 그중에 이기동부사장님의 코드를 리뷰하면서 리팩토링해봤다.

 

수정된 내용은 아래와 같다.

1. write문들은 맨 뒤로 보내고 하나의 문자열로 포매팅 처리 (”””) 후 한번에 출력

2. split한 결과를 별도로 저장하지 않고 split()함수 뒤에 바로 [1]를 붙여서 처리

3. if문 안에 있는 write문은 변수에 담아서 출력 문자열에 이어 붙이기

 

글이 너무 길어져서 2차 리팩토링은 따로 올리겠다. 2차 리팩토링에서는 파일선택을 다이얼로그 박스를 통해서 할 수 있도록 수정할 예정이다.

 

 



  추천하기   목록보기

Copyright 1999-2023 Zeroboard / skin by zero
구조설계의 미래를 준비하는 모임 [구조설계미래포럼]