メカエンジニアのためのプログラミング #018 アプリ開発実践例

【例題】テキストファイルの中身をターミナルに表示して、そのファイルを名前を変えて保存する。

  1. 要求を確認する
  2. 使えそうな技術をリサーチする
  3. 処理手順を考える
  4. コーディング
  5. テスト・デバッグ

1. 要求を確認する

要求は下記のように分解できる。

  • テキストファイルの中身をターミナルに表示する
  • テキストファイルを名前を変えて保存する

ここでいくつかの疑問点が生じる(下記4点)。こういった点は本来はユーザーと相談して決定する。
ユーザーによって指定されなかった項目については、開発者サイドが決定する。
※開発者サイドは、コスト・日程的に最大限ユーザーフレンドリーに仕様を決定することが望ましい。

  • テキストファイルの種類はどこまで対応するのか(csv, txt, ini……など)
  • 表示するテキストファイルはどのように指定するのか
  • 保存する際の名前はどのように指定するのか
  • ファイルの保存先はどのように指定するか

2. 使えそうな技術をリサーチする

1節で確認した要求に対してどのような技術(ライブラリー・関数など)があるかをリサーチする。
リサーチ結果:

3. 処理手順を考える(本来はエラー処理も考え、フローチャートなどでまとめる)

  1. 表示したいテキストファイルを選択する:
    ターミナルにファイルパスをフルパスで入力してもらう
    入力後,enterで処理を続行
  2. テキストファイルをPythonスクリプト上で開く(取り込む:with open())
  3. ステップ2で開いたファイルを1行読み込んでターミナルに表示
  4. ステップ3の1行分のデータを文字列としてnumpy配列に格納
  5. ファイルの中身がなくなるまで次の行をステップ2,3の処理を行う
  6. ファイルの保存場所の指定を促す文字列をターミナルに表示する
    ファイルの保存場所をフルパスで入力してもらう
  7. ファイル名の指定を促す文字列をターミナルに表示する
    ファイル名を拡張子付きで入力してもらう
  8. ステップ6,7の文字列を結合して、保存するファイルのパスのフルパスを生成
  9. numpy配列に保存した文字列をテキストファイルを生成して1行ずつ書き込む
  10. テキストファイルを生成した後、終了のメッセージをターミナルに表示する

4. コーディング

2節で調べた技術を3節で決めた順序に沿って実装していく。
1ステップごとにprint()などを使用し、想定通りの変数の中身が入っているか、想定通りに条件文の分岐が行われているかなどを確認して実装する。
下記のコードはyoutubeで音楽を聴きつつ、時々瞑想して2時間くらいの時間がかかった(実働20[min]くらいか?)。とりあえず、一発目のコードは、詳細を詰めずにざっくり実装していくのが良い。下記のコードではところどころ # デバッグ用 と書かれたところがあるが、これをコメントアウトしない状態で、常に扱っているデータがどういう状態かを気にかけつつコードを修正する必要がある。


# コード


"""
    1.  表示したいテキストファイルを選択する:
        ターミナルにファイルパスをフルパスで入力してもらう
        入力後,enterで処理を続行
    2.  テキストファイルをPythonスクリプト上で開く(取り込む:with open())
    3.  ステップ2で開いたファイルを1行読み込んでターミナルに表示
    4.  ステップ3の1行分のデータを文字列としてnumpy配列に格納
    5.  ファイルの中身がなくなるまで次の行をステップ2,3の処理を行う
    6.  ファイルの保存場所の指定を促す文字列をターミナルに表示する
        ファイルの保存場所をフルパスで入力してもらう
    7.  ファイル名の指定を促す文字列をターミナルに表示する
        ファイル名を拡張子付きで入力してもらう
    8.  ステップ6,7の文字列を結合して、保存するファイルのパスのフルパスを生成
    9.  numpy配列に保存した文字列をテキストファイルを生成して1行ずつ書き込む
    10. テキストファイルを生成した後、終了のメッセージをターミナルに表示する
"""

import encodings
from itertools import count
import numpy as np
import os
from numpy import genfromtxt
import sys
import chardet

# 1.  表示したいテキストファイルを選択する:
#     ターミナルにファイルパスをフルパスで入力してもらう
#     入力後,enterで処理を続行
print(r"処理したいファイルをフルパスで指定してください")
print(r"入力後 Enterキーを推してください")
text_imput_file = input(">>>") 
# text_imput_file = r"C:\Users\serpent\Documents\PythonScripts\study\templete.py" # デバッグ用

# print(text_imput_file) # デバッグ用

# save_path = r""
# app_path  = os.getcwd() 

file_neme_ini = text_imput_file # コピペで変数の統一が面倒なため変数を入れ直す


# ファイルをバイナリで開いて文字コードを調べる
with open(file_neme_ini, 'rb') as f:
    print(file_neme_ini)
    encoding_file_01 = chardet.detect(f.read()) # リターンは辞書型
    print(encoding_file_01) # デバッグ用:辞書のキーを確認する
    print(encoding_file_01["encoding"]) # デバッグ用:キーを指定して対応する値を見る
    print(type(encoding_file_01["encoding"])) # デバッグ用:拾いたい値の型を確認


count_lines_in_file = 0 # 開いたテキストファイルの行数 必要になるかどうかは不明
list_lines = [] # 1行の文字列をリストに格納

# 
# 開けなかったらループさせて、再度文字入力させるようにしたほうがいいか
# その場合は終了手段が必要
# 
# 2.  テキストファイルをPythonスクリプト上で開く(取り込む:with open())
# 3.  ステップ2で開いたファイルを1行読み込んでターミナルに表示
#     ファイルの中身はいったんリストに格納
# 5.  ファイルの中身がなくなるまで次の行をステップ2,3の処理を行う
if(os.path.exists(file_neme_ini)):
    with open(file_neme_ini, mode="r", encoding=encoding_file_01["encoding"]) as f:
        line = f.readline()
        
        while line:
            line_non_crlf = line.rstrip("\n")
            print(line_non_crlf) # 改行コードを消して表示、print()は呼び出すたびに改行されるので、余計な改行が入らないようにする。
            list_lines.append(line_non_crlf)    # リストの要素を追加
            count_lines_in_file += 1
            line = f.readline()

else:
    print("入力されたファイルは存在しません")
    sys.stdout = open("file.log", "w")      # ログを残す
    print("入力されたファイルは存在しません") # ログに記載するテキスト
    print(file_neme_ini)                    # ログに記載するテキスト
    sys.exit()  # プログラムを終了させる


# print(list_lines) # デバッグ用
# print(f"size={len(list_lines)}") # デバッグ用
# print(count_lines_in_file) # デバッグ用


# 4.  ステップ3の1行分のデータを文字列としてnumpy配列に格納
data_text_file_input = np.array(list_lines)

# print(data_text_file_input) # デバッグ用
# print(data_text_file_input.shape) # デバッグ用(37,)
# print(data_text_file_input.size) # デバッグ用37


# 6.  ファイルの保存場所の指定を促す文字列をターミナルに表示する
#     ファイルの保存場所をフルパスで入力してもらう
print(r"ファイルの保存先のパスを指定してください")
path_save_base_01 = input(">>> ")

# 7.  ファイル名の指定を促す文字列をターミナルに表示する
#     ファイル名を拡張子付きで入力してもらう
print(r"保存時のファイル名を拡張子付きで指定してください")
path_save_extension_01 = input(">>> ")

# 8.  ステップ6,7の文字列を結合して、保存するファイルのパスのフルパスを生成
path_save_01 = path_save_base_01 + r"\\" + path_save_extension_01
# path_save_01 = r"C:\Users\serpent\Documents\PythonScripts\study\templete_5.py" # デバッグ用

# 9.  numpy配列に保存した文字列をテキストファイルを生成して1行ずつ書き込む

type_tmp = data_text_file_input.dtype
# print(type_tmp)
type_tmp_01 = ((str)(type_tmp)).replace('<U', '')   # replace()はnp配列のstrには対応していないので普通のstrに変換
type_tmp_02 = r"U" + (str)((int(type_tmp_01) + 2))  # 2文字文追加
# str_crlf_np = np.full(data_text_file_input.shape[0], r"\n")
# data_text_file_input_01 = data_text_file_input + str_crlf_np

data_tmp_size = data_text_file_input.shape[0]

data_text_file_input_01 = np.full( data_tmp_size, r"", dtype=type_tmp_02)
# print(data_text_file_input_01.dtype)

for i in range(0, data_tmp_size):
    data_text_file_input_01[i] = data_text_file_input[i] + "\n"

with open(path_save_01, 'w', encoding=encoding_file_01["encoding"]) as file_tmp:
    file_tmp.writelines(data_text_file_input_01)

# 10. テキストファイルを生成した後、終了のメッセージをターミナルに表示する
print(r"done!")

5. テスト・デバッグ

3節で想定しきれなかった仕様通りではない部分を見つけ出す。

例えば、下記のような部分に対してどのような操作を行ってもアプリケーションが落ちないかを確認する必要がある。

  • ユーザー入力部分
    1. 日本語入力に対応しているか
    2. 存在しないファイルパスを入力しても問題ないか
    3. コピー&ペーストで入力された場合、文字コード影響が出ないか
    文字化けした文字列を入力されても問題ないか
    4. 入力されたファイルは処理可能なファイル形式か
    誤って画像のファイルパスを入力されても問題ないか
    5. 保存するファイルの名前が既存の場合、上書きしてしまわないか
  • 内部処理部分
    6. 読み込みのファイル形式は、仕様通りの拡張子に対応しているか
    7. 読み込みのファイル形式は、どの文字コードにも対応しているか
    特定の文字コードのみに対応する仕様の場合は、その旨をユーザーに周知できているか
    8. アプリケーション上で開いたファイルはアプリケーション終了時に確実に閉じられているか
    ex) エクセルなどで、他のユーザーが開いていて編集できません、と表示されるのは、ファイルを閉じる処理が不十分であることが原因
    9. アプリケーションの実行は、どのディレクトリにおいても可能か
    プログラム上でテスト用に使用した絶対パスなどが残されていないか
    10. テスト用に使用したprint()が残されていないか

その他、重複している変数名がないか、未使用の変数が無いか、スペルミスの変数名などが無いか、簡単に書けるコードがないかを確認する(このような作業をリファクタリングという)。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

トップに戻る