Hi...

Originally Posted by
josheli
...it's easier to just "make it work" and worry later about the elegance, reuse, maintainability, etc.
That way lies burn out. You have to buy some amount of refactoring time even if it's only 10% of your total ltime. Otherwise you are leaving an unmaintainable code base behind which won't do the business any good anyway. They aren't hiring a slave, they are hiring a professional. You have to exercise your professional judgement when it comes to the quality of your work. Unfortunately explaining this to management when they are inexperienced with software development is a struggle. It's bad enough in the role of consultant (they take more notice when you charge 60 quid an hour).
You have to explain that the extra investment will reduce risk (when you leave there will be less code for the next person to learn) and lead to greater productivity long term. Management understand "risk", "options", "training costs" and "underinvestment".
The main thing is that they should never be able to tell you how long a task should take. They set scope and can make noises about quality (something they won't understand with respect to software) and can allocate more resources. You always say how long something will take and you don't even commit to that because it's an estimate.

Originally Posted by
josheli
As to the direction, honestly I don't even know where to start, and the prospect seems daunting. (i wish we would hire another developer, preferrably smarter than me, so one of us could focus on just these issues.)
It won't be that bad if you face up to it being a gradual task over the course of a year. Even at 3 hours a week, that's a lot of design time. The trick is not to sign off on a task until you are happy with it. Not just working, but working better than the last one even if only slightly.

Originally Posted by
josheli
... (about 50 items total).
This is all in the database, right. What is the current schema for this? Do you control that schema?

Originally Posted by
josheli
the system produce reports (in HTML and Excel) on the number of correspondences handled per employee per arbitrary time period (Oct. 21-Nov. 8),
All of this can be done on the query side. Let's say that we give this query a class...
PHP Code:
class EmployeeProductivity {
function EmployeeProductivity($start, $end) { ... }
function &execute(&$connection) {
$result = $connection->query($this->getSql());
return new Table($result, $this->getTypes());
}
function getSql() {
return "select employee, count(employee) as number, ...";
}
function getTypes() {
return array('number' => 'IntegerField', 'employee' => 'StringField');
}
}
class Table {
function Table(&$result, $types) { ... }
function nextRow() {
$row = array();
$data = $this->_result->nextRow();
foreach ($data as $field => $value) {
$class = $this->_types[$field];
$row[$field] = new $class($value);
}
return $row;
}
}
class IntegerField {
function IntegerField($value) { ... }
function asString() {
return (string)$this->_value;
}
}
The plan is to move all of the stuff that is specific to retrieving the data for a report into just that one class. This is nothing complicated. The EmployeeProductivity class is just a glorified wrapper for the SQL query. All we are doing is making sure that enough information is returned (the types) so that the data can be displayed without knowing anything about where it came from.
The Table class is generic. You will only have to write it once. The Field classes are simply data holders that know how to display themselves. Simple for an integer, but you could also have MoneyField and DateField an deven subtypes of these. The main thing is that the report will only have to call asString() on each column without having to worry about all of that.
This means that the Report class can be left to focus only on display issues and will eventually be able to work with any type of data. Ideally we want to only write a single Report class for every type of report.

Originally Posted by
josheli
broken down by issue (complaint, company fraud, consumer issue, etc.).
One step at a time. Just create a class for each of these.

Originally Posted by
josheli
another report is a Word Perfect document listing all contact information for all correspondences concerning a certain issue during an arbitrary time period.
Ignore the Word Perfect part for the moment. Get the query class set up so that it returns the correct table. I'll deal with the formatting next.

Originally Posted by
josheli
another report would be a PDF dump for the public of all their correspondences with us. there are a dozen other reports. some are run by batch job, some are run manually through the browser. in most cases the logic looks like this:
Well you only have to write a constructor and some simple declarative methods for each. that's lot's of very small classes right?

Originally Posted by
josheli
my first off-the-cuff guess at something else would be:
PHP Code:
$report =& new EmployeeProductivityReport();
$report->setFormat(new ExcelWriter());
$report->setCriteria($startDate, $endDate, $issue);
$report->generate();
If the report is divorced from the query that generated it, then it's all in the parameters...
PHP Code:
$report = &new Report('Employee Productivity');
$report->addTable(new Employee Productivity($startDate, $endDate, $issue));
$report->generate(new ExcelWriter());
Report is going to end up a complicated class, but it's a class that get's written once. Here is how I am guessing it will work underneath...
PHP Code:
class Report {
var $_title;
var $_elements;
function Report($title) {
$this->_title = $title;
$this->_elements = array();
}
function addTable(&$table) {
$this->_elements[] = &new TableView($table);
}
function generate(&$writer) {
$writer->writeTitle($this->_title);
for ($i =0; $i < count($this->_elements); $i++) {
$this->_elements[$i]->generate($writer);
}
}
}
class TableView {
function TableView(&$table) { ... }
function generate(&$writer) {
$writer->startTable();
$writer->addTableHeader(array_keys($this->_table->getTypes()));
while ($row = $this->_table->nextRow()) {
$writer->addRow(array_values($row));
}
$writer->endTable();
}
}
class ExcelWriter {
...
function addRow($fields) {
foreach ($fields as $field) {
$this->paintCell($field->asString());
}
}
}
Basically there is nothing report specific in this code, it's all generic.

Originally Posted by
josheli
should the Report class really know how to calculate employee productivity?
No. For this you need a decorator (a pattern) around the Table. the decorator would fiddle withthe rows as they pass through, adding additional columns, etc. Are you familiar with the decorator pattern?

Originally Posted by
josheli
should the Report class be validating the criteria?
Do taht in the top level script, the one that assembled the report object.

Originally Posted by
josheli
what if i want a version where the user only needs to see half the fields in the report, do i subclass or add a setFieldsToSee() method?
Either write another query class or write another decorator. The decorator is less work long term.

Originally Posted by
josheli
what if a Super User can see fields 1,3,5 but an administrator can see all fields?
Either a different query class or a decorator again. I think I would go for a different query class in this case as there are likely to be other issues.

Originally Posted by
josheli
should the Report know about prompting a download in a browser, or writing to a file, or emailing the report? the list is endless.

They are just different types of Writer classes. All they have to do is print something when the Report send them an event.

Originally Posted by
josheli
the idea of commonality among applications brings up another problem. each application is its own private Idaho, but i try to reuse as much of a single code base among all the 35+ applications as i can.
Thirty five
!
Ok, what is the churn rate on this code? That is how often are each of these legacy reports changed?

Originally Posted by
josheli
i have to go back to previous applications and alter the application interface to match the new Reporting version. either that or I maintain multiple versions of the Reporting classes, or worse, multiple instances of multiple versions local to each application. i really don't have the resources to do that (not to mention my boss would be livid for me "fixing" a "working" application). any ideas?.
You have to treat each one as a tactical decision. If a report is likely to be heavily maintained then you will definitely want to keep it in sync. Regarding backporting, that's tactical too. Hopefully pretty soon you will be writing a lot less code and that may free up some time to tackle the backlog. Otherwise do it on need.
Don't forget that a lot of the time when you add some functionality to the Reporting/Writer side of things that will become available to the other reports. Sweeping changes that would have been a nightmare before should become easier. Hopefully you will win overall...
Now I am coding at a distance here, so everything I say may be completely wrong. Does any of this resonate with your current predicament.
yours, Marcus
Bookmarks