What? You didn't know that? :-)
On a recent blog post, I received this comment:
So now it is time to elaborate a bit.
First of all, the hardest part of programming is not learning new features or absorbing the syntax of a programming language. After all, learning such a language is waaaaay easier than learning a human language - primarily because when we write code we are communicating with something that "thinks" quickly but is not particularly "smart." It, the computer however you want to define that these days, does what we tell it to do. Really, it does - no matter how many sci-fi movies you've watched that indicate otherwise.
Since a computer isn't very smart, we have to communicate with it using very formal, stiff syntax. That's a pain, but it also makes things easier.
Anyway, the hardest part of programming is that programming is performed by humans, and we are complicated, contradictory, flawed organisms. Thus, the social processes that envelop the programming tasks are usually a bigger challenge (and opportunity) than focusing on the technology itself (that is, go to another class on Advanced PL/SQL, and so on).
So when it comes to the question of "How do get change-resistant crusty, old programmers to change their behaviors?", the answers are too fold:
We change when we feel it is in our best interest to do so - or we change without quite realized it, because someone was very clever in how they approached things.
So my suggestions for being clever and incentivizing for change:
1. Identify the behaviors you want to see changed.
3. Make it as easy as possible to make the change.
Yes, that's right, I used the word "fun" in the context of not just software, from changing software coding behaviors. Hmmmm. Well, how do people have fun? They play games!
And there is a game you can play - individually and as a group - that will help improve your ability to identify repetitions in code and therefore opportunities for reuse. That game is Set. Set is a wonderfully simple and elegant game. I encourage you to buy the actual deck of cards (but, yes, you can play it online and there is an app, of course).
Play this game with your kids (if they are young enough to still want to play games with you) and play it together as a team. It's fun - and it's all about enhancing your pattern analysis skills.
On a recent blog post, I received this comment:
Thanks for the video. You've answered my question and given us lots to talk over. Now, do you have any advice on getting those resistant to change, crusty, old, developers to buy in to change?My immediate response was: incentives and fun.
So now it is time to elaborate a bit.
First of all, the hardest part of programming is not learning new features or absorbing the syntax of a programming language. After all, learning such a language is waaaaay easier than learning a human language - primarily because when we write code we are communicating with something that "thinks" quickly but is not particularly "smart." It, the computer however you want to define that these days, does what we tell it to do. Really, it does - no matter how many sci-fi movies you've watched that indicate otherwise.
Since a computer isn't very smart, we have to communicate with it using very formal, stiff syntax. That's a pain, but it also makes things easier.
Anyway, the hardest part of programming is that programming is performed by humans, and we are complicated, contradictory, flawed organisms. Thus, the social processes that envelop the programming tasks are usually a bigger challenge (and opportunity) than focusing on the technology itself (that is, go to another class on Advanced PL/SQL, and so on).
So when it comes to the question of "How do get change-resistant crusty, old programmers to change their behaviors?", the answers are too fold:
- Recognize that we are all change-resistant, not just the Wise Elders.
- We (humans in general) change when we are incentivized to change.
We change when we feel it is in our best interest to do so - or we change without quite realized it, because someone was very clever in how they approached things.
So my suggestions for being clever and incentivizing for change:
- Identify the behaviors you want to see changed.
- Figure out how to measure the change.
- Make it as easy as possible to make the change.
- Make the change as fun as possible.
- Reward us as and when we go through the transformation.
Let's look at an example. Suppose that we have a problem in our development team that code re-use is pitiful. Instead different members of the team write the same or similar stuff, over and over again. How can we get the behavior to change?
1. Identify the behaviors you want to see changed.
I want my team to find and re-use existing subprograms in package APIs, rather than rewrite stuff.
2. Figure out how to measure the change.
How can I tell when people are reusing code? Ah, the beauty of PL/SQL - code is stored in the database and metadata about that code is stored in various data dictionary views. Of most relevance here is PL/Scope and the ALL_IDENTIFIERS view. I can run a query that shows me how many places a particular or all subprograms are invoked.
I've attached a script at the bottom of this post that Jan-Hendrik van Heusden provided to me, which you might find helpful in this area.
I've also posted a PL/Scope helper package on LiveSQL that may help get you started on PL/Scope more generally.
I've attached a script at the bottom of this post that Jan-Hendrik van Heusden provided to me, which you might find helpful in this area.
I've also posted a PL/Scope helper package on LiveSQL that may help get you started on PL/Scope more generally.
3. Make it as easy as possible to make the change.
Now, this is a tough one. How do we make our developers more aware of what they can re-use and how? I do not have a simple or "packaged" solution for you, so I will instead offer some ideas:
- Standardize documentation of program headers so you can extract documentation of those subprograms
- Build an intranet site (using Application Express!) to display this generated documentation or make it easy to search the code catalog (ALL_SOURCE)
- Monthly code review: highlight new program units and potential code re-use opportunities
4. Make the change as fun as possible.
Yes, that's right, I used the word "fun" in the context of not just software, from changing software coding behaviors. Hmmmm. Well, how do people have fun? They play games!
And there is a game you can play - individually and as a group - that will help improve your ability to identify repetitions in code and therefore opportunities for reuse. That game is Set. Set is a wonderfully simple and elegant game. I encourage you to buy the actual deck of cards (but, yes, you can play it online and there is an app, of course).
Play this game with your kids (if they are young enough to still want to play games with you) and play it together as a team. It's fun - and it's all about enhancing your pattern analysis skills.
5. Reward us as and when we go through the transformation.
Oh how we humans like our rewards! They can come in roughly two forms: tangible and intangible.
Examples of tangible rewards: gift cards, promotions, raises, etc.
Examples of intangible rewards: recognition, mostly.
In terms of promoting re-use, I could envision a monthly or quarterly awarding of prizes for (a) who built the code that is reused the most (likely a more senior developer) and (b) who is reusing code most effectively (likely a more junior developer).
How could you tell? Again, PL/Scope would help this.
=================
Well, I hope you find these ideas helpful. No doubt about it, it is way harder to change human patterns of behavior than to pick up new technical skills.
In terms of promoting re-use, I could envision a monthly or quarterly awarding of prizes for (a) who built the code that is reused the most (likely a more senior developer) and (b) who is reusing code most effectively (likely a more junior developer).
How could you tell? Again, PL/Scope would help this.
=================
Well, I hope you find these ideas helpful. No doubt about it, it is way harder to change human patterns of behavior than to pick up new technical skills.
Using PL/Scope to analyse identifier usages in PL/SQL
/*
From: Jan-Hendrik van Heusden
Sent: Tuesday, September 17, 2013 10:49 AM
To: Steven Feuerstein
Subject: Query to analyse identifier usages in PL/SQL
Hi Steven,
I was working in a project where quite some obsolete code was being removed,
and I wanted to know if certain methods, variables were actually used yet.
I first made sure that everything was compiled with the appropriate settings (IDENTIFIERS:ALL).
Then I created the attached query, I thought you might be interested in it.
Note that this query does of course not show all possible usages;
it does not list usages in views or over database links, and it definitely
does not list usage in external souces (sql files, queries made from external applications etc.).
Nevertheless I found it very useful for what it does (usage of PL/SQL
identifiers within PL/SQL). Other PL/SQL developers may be interested,
so feel free to use or publish it. You may also want to change things like
the sorting order, feel free to do so.
Best regards,
Jan-Hendrik van Heusden
*/
SELECT pl_id.declaring_owner,
pl_id.declaring_object_type,
pl_id.declaring_object_name,
pl_id.declaring_line,
pl_id.declared_type,
pl_id.declared_name,
pl_id.overload,
pl_id.referring_owner,
pl_id.referring_object_type,
pl_id.referring_object_name,
pl_id.usage,
pl_id.external_call,
pl_id.referring_line,
pl_id.referring_column,
pl_id.referring_source_text,
CASE
WHEN pl_id.usage = 'DECLARATION'
AND pl_id.declared_type NOT IN ('PACKAGE', 'TRIGGER')
THEN
COUNT (
CASE
WHEN pl_id.usage NOT IN ('DECLARATION', 'DEFINITION')
THEN
1
END)
OVER (PARTITION BY pl_id.signature)
END
nr_of_references,
CASE
WHEN pl_id.usage = 'DECLARATION'
AND pl_id.declared_type NOT IN ('PACKAGE',
'FUNCTION',
'PROCEDURE',
'TRIGGER')
THEN
COUNT (CASE
WHEN pl_id.usage NOT IN ('DECLARATION',
'DEFINITION',
'ASSIGNMENT',
'CALL')
THEN
1
END)
OVER (PARTITION BY pl_id.signature)
END
var_read_references,
CASE
WHEN pl_id.usage = 'DECLARATION'
AND pl_id.declaring_object_type != 'TRIGGER'
AND pl_id.declared_type NOT IN ('PACKAGE', 'TRIGGER', 'LABEL')
THEN
COUNT (pl_id.external_call) OVER (PARTITION BY pl_id.signature)
END
nr_of_external_references,
CASE
WHEN UPPER (
SUBSTR (pl_id.referring_source_text,
pl_id.referring_column,
LENGTH (pl_id.declared_name))) =
pl_id.declared_name
THEN
REGEXP_SUBSTR (
SUBSTR (pl_id.referring_source_text,
1,
pl_id.referring_column - 1),
'("?[a-zA-Z][a-zA-Z0-9_$#]*"?[.%]"?)*"?$')
|| SUBSTR (pl_id.referring_source_text,
pl_id.referring_column,
LENGTH (pl_id.declared_name))
|| REGEXP_SUBSTR (
SUBSTR (
pl_id.referring_source_text,
pl_id.referring_column + LENGTH (pl_id.declared_name),
1),
'"')
END
qualified_called_name,
pl_id.signature
FROM (WITH pl_declaration
AS (SELECT decl.*,
CASE
WHEN TYPE IN ('FUNCTION', 'PROCEDURE')
THEN
DENSE_RANK ()
OVER (PARTITION BY decl.owner,
decl.object_type,
decl.object_name,
decl.TYPE,
decl.name
ORDER BY decl.line, decl.col)
END
overload
FROM all_identifiers decl
WHERE decl.usage = 'DECLARATION' -- Leave out calls to Oracle internals
AND decl.owner != 'SYS')
SELECT pld.owner declaring_owner,
pld.object_type declaring_object_type,
pld.object_name declaring_object_name,
pld.line declaring_line,
pld.TYPE declared_type,
pld.name declared_name,
pld.overload,
i.owner referring_owner,
i.object_type referring_object_type,
i.object_name referring_object_name,
i.usage,
CASE
WHEN i.object_name != pld.object_name
OR i.owner != pld.owner
THEN
'External'
END
external_call,
i.line referring_line,
i.col referring_column,
(SELECT s.text
FROM all_source s
WHERE s.owner = i.owner
AND s.TYPE = i.object_type
AND s.name = i.object_name
AND s.line = i.line)
referring_source_text,
pld.signature
FROM pl_declaration pld
JOIN all_identifiers i ON i.signature = pld.signature) pl_id
WHERE -- leave out obvious references to a package, these are always redundant
-- because you can not just refer to a packagewithout referring
-- to a subprogram, constant etc.; these are included in the query result anyway
NOT (pl_id.declared_type = 'PACKAGE' AND pl_id.usage = 'REFERENCE')
-- leave out obvious self references to a type, these are always redundant
AND NOT ( pl_id.declared_type = 'TYPE'
AND pl_id.usage = 'REFERENCE'
AND pl_id.declaring_owner = pl_id.referring_owner
AND pl_id.declaring_object_name = pl_id.referring_object_name
AND pl_id.declaring_object_type = 'TYPE'
AND pl_id.referring_object_type = 'TYPE')
AND ( pl_id.declaring_owner IN (USER,
SYS_CONTEXT ('userenv',
'current_schema'))
OR pl_id.referring_owner IN (USER,
SYS_CONTEXT ('userenv',
'current_schema')))
ORDER BY pl_id.declaring_owner,
CASE pl_id.declaring_object_type
WHEN 'PACKAGE' THEN 10
WHEN 'PACKAGE BODY' THEN 20
WHEN 'TYPE' THEN 30
WHEN 'TYPE BODY' THEN 40
WHEN 'FUNCTION' THEN 50
WHEN 'PROCEDURE' THEN 60
WHEN 'TRIGGER' THEN 70
ELSE 900
END,
pl_id.declaring_object_name,
CASE WHEN pl_id.declared_type LIKE 'FORMAL%' THEN 20 ELSE 10 END,
CASE pl_id.declared_type
WHEN 'PACKAGE' THEN 10
WHEN 'PACKAGE BODY' THEN 15
WHEN 'TYPE' THEN 20
WHEN 'TYPE BODY' THEN 20
WHEN 'FUNCTION' THEN 20
WHEN 'PROCEDURE' THEN 20
WHEN 'CURSOR' THEN 30
WHEN 'REFCURSOR' THEN 40
WHEN 'CONSTANT' THEN 50
WHEN 'NESTED TABLE' THEN 60
WHEN 'VARRAY' THEN 70
WHEN 'RECORD' THEN 80
WHEN 'VARIABLE' THEN 90
ELSE 500
END,
pl_id.declared_type,
pl_id.declared_name,
pl_id.overload,
pl_id.signature,
CASE pl_id.usage
WHEN 'DECLARATION' THEN 10
WHEN 'DEFINITION' THEN 20
WHEN 'CALL' THEN 600
WHEN 'ASSIGNMENT' THEN 600
WHEN 'REFERENCE' THEN 900
ELSE 500
END,
pl_id.external_call ASC NULLS FIRST,
pl_id.usage,
pl_id.referring_owner,
pl_id.referring_object_type,
pl_id.referring_object_name,
pl_id.referring_line,
pl_id.referring_column
/
/*
From: Jan-Hendrik van Heusden
Sent: Tuesday, September 17, 2013 10:49 AM
To: Steven Feuerstein
Subject: Query to analyse identifier usages in PL/SQL
Hi Steven,
I was working in a project where quite some obsolete code was being removed,
and I wanted to know if certain methods, variables were actually used yet.
I first made sure that everything was compiled with the appropriate settings (IDENTIFIERS:ALL).
Then I created the attached query, I thought you might be interested in it.
Note that this query does of course not show all possible usages;
it does not list usages in views or over database links, and it definitely
does not list usage in external souces (sql files, queries made from external applications etc.).
Nevertheless I found it very useful for what it does (usage of PL/SQL
identifiers within PL/SQL). Other PL/SQL developers may be interested,
so feel free to use or publish it. You may also want to change things like
the sorting order, feel free to do so.
Best regards,
Jan-Hendrik van Heusden
*/
SELECT pl_id.declaring_owner,
pl_id.declaring_object_type,
pl_id.declaring_object_name,
pl_id.declaring_line,
pl_id.declared_type,
pl_id.declared_name,
pl_id.overload,
pl_id.referring_owner,
pl_id.referring_object_type,
pl_id.referring_object_name,
pl_id.usage,
pl_id.external_call,
pl_id.referring_line,
pl_id.referring_column,
pl_id.referring_source_text,
CASE
WHEN pl_id.usage = 'DECLARATION'
AND pl_id.declared_type NOT IN ('PACKAGE', 'TRIGGER')
THEN
COUNT (
CASE
WHEN pl_id.usage NOT IN ('DECLARATION', 'DEFINITION')
THEN
1
END)
OVER (PARTITION BY pl_id.signature)
END
nr_of_references,
CASE
WHEN pl_id.usage = 'DECLARATION'
AND pl_id.declared_type NOT IN ('PACKAGE',
'FUNCTION',
'PROCEDURE',
'TRIGGER')
THEN
COUNT (CASE
WHEN pl_id.usage NOT IN ('DECLARATION',
'DEFINITION',
'ASSIGNMENT',
'CALL')
THEN
1
END)
OVER (PARTITION BY pl_id.signature)
END
var_read_references,
CASE
WHEN pl_id.usage = 'DECLARATION'
AND pl_id.declaring_object_type != 'TRIGGER'
AND pl_id.declared_type NOT IN ('PACKAGE', 'TRIGGER', 'LABEL')
THEN
COUNT (pl_id.external_call) OVER (PARTITION BY pl_id.signature)
END
nr_of_external_references,
CASE
WHEN UPPER (
SUBSTR (pl_id.referring_source_text,
pl_id.referring_column,
LENGTH (pl_id.declared_name))) =
pl_id.declared_name
THEN
REGEXP_SUBSTR (
SUBSTR (pl_id.referring_source_text,
1,
pl_id.referring_column - 1),
'("?[a-zA-Z][a-zA-Z0-9_$#]*"?[.%]"?)*"?$')
|| SUBSTR (pl_id.referring_source_text,
pl_id.referring_column,
LENGTH (pl_id.declared_name))
|| REGEXP_SUBSTR (
SUBSTR (
pl_id.referring_source_text,
pl_id.referring_column + LENGTH (pl_id.declared_name),
1),
'"')
END
qualified_called_name,
pl_id.signature
FROM (WITH pl_declaration
AS (SELECT decl.*,
CASE
WHEN TYPE IN ('FUNCTION', 'PROCEDURE')
THEN
DENSE_RANK ()
OVER (PARTITION BY decl.owner,
decl.object_type,
decl.object_name,
decl.TYPE,
decl.name
ORDER BY decl.line, decl.col)
END
overload
FROM all_identifiers decl
WHERE decl.usage = 'DECLARATION' -- Leave out calls to Oracle internals
AND decl.owner != 'SYS')
SELECT pld.owner declaring_owner,
pld.object_type declaring_object_type,
pld.object_name declaring_object_name,
pld.line declaring_line,
pld.TYPE declared_type,
pld.name declared_name,
pld.overload,
i.owner referring_owner,
i.object_type referring_object_type,
i.object_name referring_object_name,
i.usage,
CASE
WHEN i.object_name != pld.object_name
OR i.owner != pld.owner
THEN
'External'
END
external_call,
i.line referring_line,
i.col referring_column,
(SELECT s.text
FROM all_source s
WHERE s.owner = i.owner
AND s.TYPE = i.object_type
AND s.name = i.object_name
AND s.line = i.line)
referring_source_text,
pld.signature
FROM pl_declaration pld
JOIN all_identifiers i ON i.signature = pld.signature) pl_id
WHERE -- leave out obvious references to a package, these are always redundant
-- because you can not just refer to a packagewithout referring
-- to a subprogram, constant etc.; these are included in the query result anyway
NOT (pl_id.declared_type = 'PACKAGE' AND pl_id.usage = 'REFERENCE')
-- leave out obvious self references to a type, these are always redundant
AND NOT ( pl_id.declared_type = 'TYPE'
AND pl_id.usage = 'REFERENCE'
AND pl_id.declaring_owner = pl_id.referring_owner
AND pl_id.declaring_object_name = pl_id.referring_object_name
AND pl_id.declaring_object_type = 'TYPE'
AND pl_id.referring_object_type = 'TYPE')
AND ( pl_id.declaring_owner IN (USER,
SYS_CONTEXT ('userenv',
'current_schema'))
OR pl_id.referring_owner IN (USER,
SYS_CONTEXT ('userenv',
'current_schema')))
ORDER BY pl_id.declaring_owner,
CASE pl_id.declaring_object_type
WHEN 'PACKAGE' THEN 10
WHEN 'PACKAGE BODY' THEN 20
WHEN 'TYPE' THEN 30
WHEN 'TYPE BODY' THEN 40
WHEN 'FUNCTION' THEN 50
WHEN 'PROCEDURE' THEN 60
WHEN 'TRIGGER' THEN 70
ELSE 900
END,
pl_id.declaring_object_name,
CASE WHEN pl_id.declared_type LIKE 'FORMAL%' THEN 20 ELSE 10 END,
CASE pl_id.declared_type
WHEN 'PACKAGE' THEN 10
WHEN 'PACKAGE BODY' THEN 15
WHEN 'TYPE' THEN 20
WHEN 'TYPE BODY' THEN 20
WHEN 'FUNCTION' THEN 20
WHEN 'PROCEDURE' THEN 20
WHEN 'CURSOR' THEN 30
WHEN 'REFCURSOR' THEN 40
WHEN 'CONSTANT' THEN 50
WHEN 'NESTED TABLE' THEN 60
WHEN 'VARRAY' THEN 70
WHEN 'RECORD' THEN 80
WHEN 'VARIABLE' THEN 90
ELSE 500
END,
pl_id.declared_type,
pl_id.declared_name,
pl_id.overload,
pl_id.signature,
CASE pl_id.usage
WHEN 'DECLARATION' THEN 10
WHEN 'DEFINITION' THEN 20
WHEN 'CALL' THEN 600
WHEN 'ASSIGNMENT' THEN 600
WHEN 'REFERENCE' THEN 900
ELSE 500
END,
pl_id.external_call ASC NULLS FIRST,
pl_id.usage,
pl_id.referring_owner,
pl_id.referring_object_type,
pl_id.referring_object_name,
pl_id.referring_line,
pl_id.referring_column
/
Nice list, I hope it gets expanded as more people add their ideas. With regard to #2, one must keep in mind the dangers of measuring productivity - e.g. when you reward someone based on code reuse, there is a real risk that you substitute the desired behaviour (reusing code *appropriately*) for undesired behaviour (reusing code whenever possible, even when not needed or appropriate). Never forget the bug bounty fiasco: http://thedailywtf.com/articles/The-Defect-Black-Market
ReplyDeleteExcellent point, Jeff.
ReplyDelete