See? We Don’t Need a Database … Yet

Here’s the current code, and some commentary, for the “But We Need a Database” article. UPDATED: Comments on Python style.

First, the Tests

Nothing new to see here. These are in the order they were written, which is my usual practice with new code. If a test suite gets large, I’d break it up, and perhaps arrange tests by topic area. As a rule, though, I like them to tell the story of how the program grew.

import unittest
from membercollection import MemberCollection
from memberrecord import MemberRecord

class NoDBTests(unittest.TestCase):

    personOne = 1
    personTwo = 2
    personSix = 6

    def test_hookup(self):
        self.assertTrue(True)

    def test_memberCharge(self):
        member = MemberRecord()
        chargeAmount = member.charge(10)
        self.assertEqual(10,chargeAmount)

    def test_sixthOneFree(self):
        member = MemberRecord()
        chargeAmount = member.charge(5)
        self.assertEqual(5, chargeAmount)
        chargeAmount = member.charge(5)
        self.assertEqual(5, chargeAmount)
        chargeAmount = member.charge(5)
        self.assertEqual(5, chargeAmount)
        chargeAmount = member.charge(5)
        self.assertEqual(5, chargeAmount)
        chargeAmount = member.charge(5)
        self.assertEqual(5, chargeAmount)
        chargeAmount = member.charge(5)
        self.assertEqual(0, chargeAmount)

    def test_twelfthOneFree(self):
        member = MemberRecord()
        for i in range(5):
            self.assertEquals(6,member.charge(6))
        chargeAmount = member.charge(6)
        self.assertEqual(0, chargeAmount, "first one bad")
        for i in range(5):
            self.assertEquals(6,member.charge(6))
        chargeAmount = member.charge(6)
        self.assertEqual(0, chargeAmount, "second one bad")

    def test_memberTwoStillGetsFree(self):
        members = MemberCollection()
        for purchaseFromTwo in range(5):
            self.assertEquals(4, members.member(self.personTwo).charge(4))
        self.assertEquals(0, members.member(self.personTwo).charge(4))

    def test_memberSixDoesNotInterfereWithTwo(self):
        members = MemberCollection()
        for purchaseFromTwo in range(5):
            self.assertEquals(4, members.member(self.personTwo).charge(4))
        self.assertEquals(4, members.member(self.personSix).charge(4), "six interfered")
        self.assertEquals(0, members.member(self.personTwo).charge(4), "two got no discount")

    def test_threeAtOnce(self):
        members = MemberCollection()
        for purchaseNumber in range(5):
            for person in [2, 6, 1]:
                self.assertEquals(3, members.member(person).charge(3), "improper free one")
        self.assertEqual(0, members.member(self.personOne).charge(3))
        self.assertEqual(0, members.member(self.personTwo).charge(3))
        self.assertEqual(0, members.member(self.personSix).charge(3))

if __name__ == '__main__':
    unittest.main()

 

The MemberRecord

This, too, is pretty much as we’ve seen it. It may be worth mentioning that the member variable __purchaseCount is private, by virtue of the two leading underbars, and that memberNumber is public. We wanted it public so that our collection code could access it.

As far as I know, there is no access indicator for read but not write. If I really cared, I’d build an accessor method and use a private variable. However, I’m usually not very concerned that someone might mistakenly write into a member record. I’m used to working with teams who have an agreed approach to their code. If I were writing a library for use by outsiders, I might be more paranoid.

__author__ = 'Ron'
class MemberRecord(object):

    def __init__(self, memberNumber=-1):
        self.__purchaseCount = 0
        self.memberNumber = memberNumber

    def charge(self, amount):
        self.__purchaseCount+=1
        return amount if self.__purchaseCount%6 != 0 else 0

 

The MemberCollection

Here, I changed the way the member(memberNumber) function works, using an explicit loop rather than my overly clever approach of filtering and then checking the result size. Note, however, that the explicit loop would be a Very Bad Idea if we were to translate it directly into a database solution. We wouldn’t want to read the database one record at a time. The filter approach might better suggest what to do to someone building the  database code. Again, my normal approach is to trust the team. YMMV.

from memberrecord import MemberRecord

__author__ = 'Ron'
class MemberCollection(object):

    def __init__(self):
        self.members = []

    def member(self, memberNumber):
        for mr in self.members:
            if mr.memberNumber == memberNumber:
                return mr
        newMember = MemberRecord(memberNumber)
        self.members.append(newMember)
        return newMember

 

Better Idea

Ori Peleg commented on the earlier article that there is a better way to do my member finding method, using a dictionary. When I worked on another practice program, I had more than one entry with the same (partial) key, so needed to filter or loop. Somehow that stuck in my mind and I didn’t think to use a dictionary, though I had tried one in the other location.

I blame my pair, Chet, for not being there. Anyway, the better way is:

 def member(self, memberNumber):
        if memberNumber in self.members:
            return self.members[memberNumber]
        else:
            newMember = MemberRecord(memberNumber)
            self.members[memberNumber] = newMember
            return newMember

 

Comments on Python Style

The code here is not in official Python style. In particular, it seems that Pythonistas prefer send_player_to_jail over sendPlayerToJail. That said, the guide linked to above does allow for use of mixedCase where that is the prevailing style.

One thing you’d have to think about if adding Python to the development mix on your team is what style to follow. At the time I wrote this program, I knew that underbar_names were preferred, but because I’m used to mixedCase, I used it to keep my cognitive load down.  I was sure someone would sort me out on it, and my luck was in. Oddly enough, Python folks do use CapWords for class names but not mixedCase for methods. Curious.

There may be other deviations: the guide referenced above is long and detailed. If I publish more Python code, I’ll see about coming more in line with convention. Meanwhile, I’ll refactor this program just to see how easy PyCharm makes it, and to bring it more in line. I won’t trouble you with another version unless I feel the need for more feedback.

Thanks for your help and comments, I do appreciate them.

Bottom Line … For Now

There are probably more things we could change here, either to make the code look more like good Python, or to make it more clear, or for some other reason. Since my point in the previous article was just to show how I’d proceed to solve a database problem without involving the database yet, I think the point is made.

Your comments are welcome …

Posted on:

Written by: Ron Jeffries

Categorization: Articles

2 Responses to “See? We Don’t Need a Database … Yet”

Richard Holden

June 22, 2011

3:02 pm

permalink

“__purchaseCount is private, by virtue of the two leading underbars, and that memberNumber is public. We wanted it public so that our collection code could access it.”

Technically the two leading underbars are an indication to other coders that they should treat the variable as if it were private, there is no true private in Python.

Also for accessor methods (getters and setters) look at the @property decorator. http://docs.python.org/library/functions.html#property

Ron Jeffries

June 22, 2011

6:59 pm

permalink

Ah, thanks, good input. I misinterpreted the page I quickly read. :)

Recent Articles

Build it right to build the right thing

You can’t build the right product if you can’t build the product right.

I tweeted that this morning, in response to a Twitter-vibe that was flitting around. I’d like to follow it up here with a bit more meat.

The …

Estimate This! (or not)

My friends and colleagues in the #NoEstimates circles have some good ideas, as I've commented elsewhere. I'm left, though, with a couple of concerns.