うーん。これは醜いです。美しいことは醜いことよりも優れています。私はもはや主に Python プログラマーではないので、このためのより良いツールがあるかもしれません。しかし、概念レベルから問題に取り組みましょう。
これは、問題を過度に複雑にする標準的な命令型プログラミングです。あなたが抱えている問題のように、実装のノイズで迷子になりやすくなります。木を見て森を見ないようにします。別のアプローチを試してみましょう。
何をする必要があるかに焦点を当て、そこから実装を始めましょう。まず、ファイルから読み取る必要があることがわかります。
ファイルから読み取る
Scenario: Calculate totals within region database
Feature: Read from database
As a user, in order to be able to view the total sales of my books and differentiate them by fiction and nonfiction, I want to be able to read data from a file.
Given: I have a file that has region data, for example data.text
When: I load data from it
Then: I should have associated region data available in my program.
テスト ケースとしての Python 実装を次に示します。
import unittest
class RegionTests(unittest.TestCase):
def testLoadARegionDatabase(self):
"""Given a region file,when I load it, then it should be stored in memory"""
# Given region database
regionDatabase = []
# When I load it
with open('./regions.txt','r') as f:
regionDatabase = f.readlines()
# Then contents should be available
self.assertTrue(len(regionDatabase) > 0)
ファイルから領域データを取得
概念的には、そのファイルのすべての行に意味があることがわかっています。基本的に、すべての行はRegionです。コード、フィクションの売上、ノンフィクションの売上、および税率をファイルに保存しました。Explicit は Implicit よりも優れているため、Region の概念には、システム内で明示的な第一級の表現が必要です。
Feature: Create a Region
As a user, in order to be able to know a region is information--including nonfiction sales, fiction sales, and tax rate-- I want to be able to create a Region.
Given: I have data for fiction sales, non-fiction sales, and tax rate
When: I create a Region
Then: Its sales, non-fiction sales, and tax-rate should be set accordingly
テスト ケースとしての Python 実装を次に示します。
def testCreateRegionFromData(self):
"""Given a set of data, when I create a region, then its non-fiction sales, fiction sales,and tax rate should be set"""
# Given a set of data
texas = { "regionCode": "TX", "fiction" : 415, "nonfiction" : 555, "taxRate" : 0.55 }
# When I create a region
region = Region(texas["regionCode"], texas["fiction"], texas["nonfiction"], texas["taxRate"])
# Then its attributes should be set
self.assertEquals("TX", region.code)
self.assertEquals(415, region.fiction)
self.assertEquals(555, region.nonfiction)
self.assertEquals(0.55, region.taxRate)
これは失敗します。通過させましょう。
class Region:
def __init__(self, code, fiction, nonfiction,rate):
self.code = code
self.fiction = fiction
self.nonfiction = nonfiction
self.taxRate = rate
合計を分析する
これで、システムが地域を表すことができることがわかりました。多数の地域を分析し、売上に関する要約統計を提供できるものが必要です。これをAnalystと呼びましょう。
Feature: Calculate Total Sales
As a user, in order to be able to know what is going on, I want to be able to ask an Analyst what the total sales are for my region
Given: I have a set of regions
When : I ask my Analyst what the total sales are
Then : The analyst should return me the correct answers
テスト ケースとしての Python の実装を次に示します。
def testAnalyzeRegionsForTotalNonFictionSales(self):
"""Given a set of Region, When I ask an Analyst for total non-fiction sales, then I should get the sum of non-fiction sales"""
# Given a set of regions
regions = [ Region("TX", 415, 555, 0.55), Region("MN", 330, 999, 0.78), Region("HA", 401, 674, 0.99) ]
# When I ask my analyst for the total non-fiction sales
analyst = Analyst(regions)
result = analyst.calculateTotalNonFictionSales()
self.assertEquals(2228, result)
これは失敗します。通過させましょう。
class Analyst:
def __init__(self,regions):
self.regions = regions
def calculateTotalNonFictionSales(self):
return sum([reg.nonfiction for reg in self.regions])
ここからフィクションの売り上げを推定できるはずです。
決定、決定
総売上高に関しては、興味深い設計上の決定があります。
- アナリストに地域のフィクションとノンフィクションの属性を直接読んでもらい、それらを合計する必要がありますか?
次のようにできます。
def calculateTotalSales(self):
return sum([reg.fiction + reg.nonfiction for reg in self.regions])
では、「歴史ドラマ」(フィクションとノンフィクション)などの属性を加えるとどうなるでしょうか。次に、地域を変更するたびに、地域の新しい構造を考慮に入れるためにアナリストを変更する必要があります。
いいえ、それは悪い設計上の決定です。Region は、総売上高について知る必要があることをすべて知っています。地域はその合計を報告できる必要があります。
良い選択をしましょう!
Feature: Report Total Sales
Given: I have a region with fiction and non-fiction sales
When : I ask the region for its total sales
Then: The region should tell me its total sales
テスト ケースとしての Python 実装を次に示します。
def testGetTotalSalesForRegion(self):
"""Given a region with fiction and nonfiction sales, when I ask for its total sales, then I should get the result"""
# Given a set of data
texas = { "regionCode": "TX", "fiction" : 415, "nonfiction" : 555, "taxRate" : 0.55 }
region = Region("TX", 415, 555, 0.55)
# When I ask the region for its total sales
result = region.totalSales()
# Then I should get the sum of the sales
self.assertEquals(970,result)
アナリストは言うべきであり、尋ねるな
def calculateTotalSales(self):
return sum([reg.totalSales() for reg in self.regions])
これで、このアプリケーションを作成するために必要なものはすべて揃いました。さらに、後で変更を加えた場合に使用できる自動回帰スイートがあります。何を壊したかを正確に伝えることができ、テストはアプリケーションが何であるか、何ができるかを明示的に指定します。
結果
結果のプログラムは次のとおりです。
from region import Region
from analyst import Analyst
def main():
text = readFromRegionFile()
regions = createRegionsFromText(text)
analyst = Analyst(regions)
printResults(analyst)
def readFromRegionFile():
regionDatabase = []
with open('./regions.txt','r') as f:
regionDatabase = f.readlines()
return regionDatabase
def createRegionsFromText(text):
regions = []
for line in text:
data = line.split()
regions.append(Region(data[0],data[1], data[2], data[3]))
return regions
def printResults(analyst):
totSales = analyst.calculateTotalSales()
totFic = analyst.calculateTotalFictionSales()
totNon = analyst.calculateTotalNonFictionSales()
for r in analyst.regions:
print("{0:2}{1:10}{2:13}{3:4}{4:14.2f}{5:10.2f}".format(
"", r.code, r.fiction, r.nonfiction, r.totalSales(), r.taxRate))
print("---------------------------------------------------------------------")
print("{0:11}{1:13}{2:34}{3:2}{4:8}".format(
"Total", totFic, totNon, "$", totSales))
if __name__ == "__main__":
main()
あなたが書いたものとこれを比較してください。どちらが分かりやすいですか?簡潔?次の場合、2 つの中で何を変更する必要がありますか。
- 各地域に音楽販売を追加しましたか?
- テキスト ファイルから MySQL データベースまたは Web サービス呼び出しに移動しましたか?
あなたのコンセプトを具現化してください。コードは明確、簡潔、表現力豊かにします。