Download presentation
Presentation is loading. Please wait.
Published byEstella Carroll Modified over 9 years ago
1
3rd Annual Plex/2E Worldwide Users Conference Page based on Title Slide from Slide Layout palette. Design is cacorp 2006. Title text for Title or Divider pages should be either 40 pt for short titles/28 pt for subtitles or 32 pts for longer titles/24 pt for subtitles. DATE text box is not on master and can be deleted. The date should always be 20 pts. 11A Building High Quality Functions Darryl Millington HawkBridge Pty Ltd September 21, 2007
2
Session Abstract >.>. 221 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
3
Speaker Bio >Darryl has worked as a CA 2E developer since 1988 travelling extensively as an independent consultant throughout Australia, New Zealand, USA, UK and Asia. >He is a regular speaker at CA conferences and has been speaking on CA 2E-related topics since 1991. >Darryl.Millington@HawkBridge.com 321 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
4
Session Agenda >Quality Perspectives >Developer Quality Objectives >Traceability Tips >Understandability Tips >Readability Tips >Reusability Tips >Return Code Processing >Default Return Code Values >Q&A 421 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
5
Quality Perspectives: >User Perspective External Characteristics Short Term Fit for Purpose >Developer Perspective Internal Characteristics Long Term Fit for Maintenance 521 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
6
6 Developer Quality Objectives: >Traceability The ease with which object references and usages can be determined >Understandability The ease with which a system can be comprehended at the system and detailed statement levels >Readability The ease with which the system source code can be read and understood at the detailed statement level >Reusability The extent to and ease with which parts of the system can be used in other parts or other systems
7
Traceability Tips: >Convert CON to CND context CON does not resolve to a Advantage 2E object that can be used for impact analysis, and object reference and usage 721 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
8
Traceability Tips: >Use function versions, NOT comment-out Commented-out action diagram code is used for impact analysis, and object reference and usage Non-current versioned objects can be ignored for impact analysis, and object reference and usage Gen objs : 0 Display Model Usages Model. : HBUTLMDL Total. : 8 Level. : 001 Object. : Customer Type.. : FLD Attribute.. : CDE Exclude system objs. *YES Scope.. *NEXT Filter.. *ANY Current objects only. *YES Reason.. *ALL 2=Edit 3=Copy 4=Delete object 5=Display 8=Details 10=Action diagram 13=Parms 14=GEN batch 15=GEN interactive 16=Y2CALL Opt Object Typ Atr Owner Lvl Reason __ Customer FLD CDE 000 *OBJECT __ HTChk Customer FUN DBF Order 001 *FUNPDT
9
Traceability Tips: >Bind 3GL references Use an EXCINTFUN wrapper and comment-out to bind EXCMSG, EXCUSRSRC and EXCUSRPGM 3GL references > EXC Submit Client Report - @Bound 3GL Objects (EXCINTFUN) Parameters: I RST Client.--. EXC Submit Client Report - *Messages (EXCMSG). I RST Client = PAR.Client * * Bind function used on SBMJOB command * RPT Client Report - Client (PRTFIL) * I RST Client = PAR.Client '-- 921 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
10
Traceability Tips: >Document menus Use EXCINTFUN’s to document menu options and bind references > MNU Change Request Main - @Menus (EXCINTFUN).-- * * Bind function to itself to stop accidental deletion * MNU Change Request Main - @Menus (EXCINTFUN). ADD Change Request - Change Request * Opt 01 (PMTRCD). WRK Change Requests - Change Request * Opt 02 (DSPFIL). PRT Change Requests - Change Request * Opt 03 (PRTFIL). MNU Change Request Setup - @Menus * Opt 10 (EXCINTFUN) '-- 1021 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
11
Traceability Tips: >Identify and remove obsolete objects Unreferenced Fields identified and removed by using F11 from the Edit Fields panel Op: QSECOFR QPADEV0006 2/10/04 8:18:18 DISPLAY UNREFERENCED FIELDS (C) HawkBridge Pty Ltd 1999 ? Field name Type Length Field name _ *TBD NBR 5.0 BENB _ Please Delete CDE 6 CDCD _ Customer Over Credit STS 1 BCST
12
Traceability Tips: >Identify and remove obsolete objects Unreferenced objects identified with the use of external queries or YDOCURF, then removed using the command YDLTMDLVSN with *DELETE or *HIDE Document Unreferenced Objects (YDOCURF) Type choices, press Enter. Model library name....... *MDLLIB Name, *MDLLIB Select object type....... *ANY *ANY, *ACP, *ARR, *CND... + for more values ____ Output model object list.... *NONE Name, *NONE, *MDLPRF, *USER
13
Understandability Tips: >Use consistent abbreviations Maintain a standard vocabulary of phrases and related abbreviations Use a single abbreviation technique 1321 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
14
Understandability Tips: >Use meaningful object names Use a naming syntax to convey details about internal attributes not easily displayed ::= : [ : [ ] [ ] [ ] ] ::=P | R | U | V | D | Q | S | Y ::=V ::=S ::=D Examples: R:Valid Customer :V1S S:Document Line 1421 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
15
Understandability Tips: >Document and use standard function templates > Get *Template - *Template (RTVOBJ) Parameters: I RST KEY O FLD > USER: Processing if Data record not found.--. PAR = CON By name. PGM.*Return code = CND.*Record does not exist '-- > USER: Process data record.--. PAR = DB1 By name. * Assume this is partial key access or FIFO access path. <-*QUIT '-- 1521 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
16
Readability Tips: >Use Comment Statements for documentation Keep to absolute minimum Use a prefix to make visually different Communicate why and what, not how Explain deviation from standard practice Don’t use on self explanatory blocks > USER: Processing if Data record not found.--. * Set return code to normal to ignore the error. PGM.*Return code = CND.*Normal '--. * Get Client Details. RTV All details - Client (RTVOBJ) 1621 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
17
Readability Tips: >Use blank Comment Statements for layout Allows reader to absorb previous block Never use more than one to seperate two blocks Use to separate conditions on complex CASE blocks > Check return code. On error send message..-CASE |-PGM.*Return code is *Normal | |-PGM.*Return code is *Record no longer on file | Send error message 'Account NF' | |-*OTHERWISE | Send error message 'ERR Return code invalid' '-ENDCASE 1721 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
18
Readability Tips: >Use comment on Action Statements Use to summarise parameters when they can be different on multiple calls RTV Field Detail - Field * PAR.Field Title (RTVOBJ) I RST Field Detail Type = CND.Title O Field Detail = PAR.Field Title RTV Field Detail - Field * PAR.Field Name (RTVOBJ) I RST Field Detail Type = CND.Name O Field Detail = PAR.Field Name 1821 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
19
Readability Tips: >Use comment on *COMPUTE Action Use a prefix to make visually different Describe the field being calculated including context PAR.Net Value = * CALC PAR.Net Value *COMPUTE (x1 - x2). x1: PAR.Gross Value. - : PGM.*Synon (17,7) work field. x2: PAR.VAT -...CALC PAR.Net Value 1921 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
20
Readability Tips: >Use comment on CASE and WHILE Statements Use to summarise conditions and associated actions Use a prefix to make visually different > If return code is normal, Do Something..-CASE |-PGM.*Return code is *Normal |...Do something '-ENDCASE > While return code is normal, Do something..-WHILE |-PGM.*Return code is *Normal |...Do something '-ENDCASE 2021 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
21
Readability Tips: >Limit imbedded CASE Statements > Check User is QSYSOPR. Do something or check User Type..-CASE |-PAR.User is QSYSOPR | |-*OTHERWISE | > Check User Type is Programmer. |.-CASE | |-PAR.User Type is *PGMR | |...Do something for User Type is *PGMR | '-ENDCASE '-ENDCASE > Check User, User Type. Do something..-CASE |-PAR.User is QSYSOPR | |-PAR.User Type is *PGMR |...Do something for User Type is *PGMR '-ENDCASE 2121 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
22
Readability Tips: >Avoid using *OTHERWISE condition on CASE statements for normal processing. > If User is NOT QSECOFR, Do something..-CASE |-PAR.User is QSECOFR | |-*OTHERWISE |...Do something for User is NOT QSECOFR '-ENDCASE > If User is NOT QSECOFR, Do something..-CASE |- NOT c1 | |- c1: PAR.User is QSECOFR | ‘- |...Do something for User is NOT QSECOFR '-ENDCASE 2221 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
23
Readability Tips: >Hide single negated conditions. To reduce vertical size. > If User is NOT QSECOFR, Do something..-CASE |- NOT c1 | |- c1: PAR.User is QSECOFR | ‘- |...Do something for User is NOT QSECOFR '-ENDCASE > If User is NOT QSECOFR, Do something..-CASE |- NOT PAR.User is QSECOFR |...Do something for User is NOT QSECOFR '-ENDCASE 2321 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
24
Readability Tips: >Avoid using double negatives on negated conditions. > If User is Entered, Do something..-CASE |- PAR.User is Entered |...Do something '-ENDCASE > If User is NOT Not entered, Do something..-CASE |- NOT PAR.User is Not Entered |...Do something '-ENDCASE 2421 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
25
Readability Tips: >Use *OTHERWISE condition to detect legitimate defaults. > Process return code..-CASE |-PGM.*Return code is *Record does not exist |...Do something for no record found | |-*OTHERWISE |...Do something for record found '-ENDCASE > Process return code..-CASE |-PGM.*Return code is *Record does not exist |...Do something for no record found | |-PGM.*Return code is *Normal |...Do something for record found | |-*OTHERWISE |...Do something for unexpected return code '-ENDCASE 2521 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
26
Readability Tips: >Use a null CASE Statement to block and hide inline code. > Do something.-CASE |-*OTHERWISE |...Do something '-ENDCASE 2621 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
27
Readability Tips: >Use a prefix to make EXCUSRSRC function source lines visually different from generated source lines. *...+... 1...+... 2...+... 3...+... 4...+... 5...+... 6 >> C* >> C MOVE #IAANB #OAACD >> C* 2721 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
28
Readability Tips: >Define and use void fields for trashing output parameters. RTV All - Customer * (RTVOBJ) I RST Customer = DTL.Customer O Customer Name = DTL.Customer Name O Customer Address 1 = WRK.!Void Text O Customer Address 2 = WRK.!Void Text O Customer Address 3 = WRK.!Void Text O Customer Address 4 = WRK.!Void Text O Customer Credit Limit = WRK.!Void Numeric 2821 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
29
Reusability Tips: >NEVER EVER use WRK context variables. > EXT Poser Vol.1 No.1 - Posers (EXCEXTFUN) O My Weight Total.-- | WRK.My Factor = CND.Imperial to Metric | RTV Poser Vol.1 No.1 - Posers (RTVOBJ) | B My Weight Total MAP = WRK.My Weight..--. | > USER: Process Data record. |.--. | | WRK.My Weight = DB1.Weight * WRK.My Factor. | | PAR.My Weight Total = PAR.My Weight Total + WRK.My Weight. | '--. '-- | PAR.My Weight Total = WRK.My Wieght - CON.10 ‘-- 2921 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
30
Reusability Tips: >NEVER EVER use WRK context variables. > EXT Poser Vol.1 No.1 - Posers (EXCEXTFUN) O My Weight Total N My Weight.-- | RTV Poser Vol.1 No.1 - Posers (RTVOBJ) | B My Weight Total MAP = PAR.My Weight | I My Factor = CND.Imperial to Metric | N My Weight..--. | > USER: Process Data record. |.--. | | PAR.My Weight = DB1.Weight * PAR.My Factor. | | PAR.My Weight Total = PAR.My Weight Total + PAR.My Weight. | '--. '-- | PAR.My Weight Total = PAR.My Weight - CON.10 ‘-- 3021 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
31
3121 September 2007 11A - Building High Quality Functions Copyright © 2007 CA Reusability Tips: >Reduce number of input, output and both parameters Pass key fields only between functions
32
Reusability Tips: >Separate the Problem Domain and the User Interface components into separate modules Remove business logic from device functions and encapsulate into reusable internal or external functions > USER: Process subfile record (Pre-confirm).--. > I=Invoice Customer..-CASE. ¦-RCD.*SFLSEL is Invoice. ¦ Invoice Customer - Customer *. | I Customer = RCD.Customer. ¦...Check return code. On error, send message and ignore.. ¦ PGM.*Reload subfile = CND.*YES. '-ENDCASE ‘--
33
3321 September 2007 11A - Building High Quality Functions Copyright © 2007 CA Reusability Tips: >Build modules that have a single purpose and function
34
3421 September 2007 11A - Building High Quality Functions Copyright © 2007 CA Return Code Processing: >PGM.*Return code returned implicitly from most function types by default Except EXCINTFUN and EXCUSRSRC >Indicates whether or not the called function completed normally or not
35
3521 September 2007 11A - Building High Quality Functions Copyright © 2007 CA Return Code Processing: >PGM.*Return code should be checked and processed after each inserted action >Processing return codes forces the developer to consider what each and every inserted action does should it end abnormally The essence of a Defensive Programming >Improves quality and robustness of applications Data integrity is maintained >Can lead to self recovering application errors
36
Return Code Processing: >Use a standard CASE title and hide the block Other developers only need to see it when investigating errors >ALWAYS check the *OTHERWISE condition as catch all error Ensures compatibility with future releases of application and tool >Use a standard catch all error message > Check return code. On error, send message and quit..-CASE ¦-PGM.*Return code is *Normal ¦ ¦-*OTHERWISE ¦ Send error message ‘ERR Return Code invalid’ ¦ I *Return code = PGM.*Return code ¦ I *Function = CON.Wrk Customer ¦ I *Synon name 1 = CON.Customer ¦ <-- *QUIT '-ENDCASE 3621 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
37
Return Code Processing: >Typical error processing: Ignore Quit Exit Send message and ignore Send message and quit Send message and exit >In most cases the error is bubbled up to the point in the process that can handle and then ignore the error 3721 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
38
Return Code Processing: >Ignore Processing Logic Add text to inform other developers that you are ignoring on purpose Unconditionally set return code to normal Use this if the called function is not required to continue processing.--. Do Something – A File * EXCEXTFUN. * Ignore return code. PGM.*Return code = CND.*Normal '-- 3821 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
39
Return Code Processing: >Quit Processing Logic Add consistent standard text to CASE statement Use this if error processing is being handled at the point where quit branches to in the function Beware of using quit to terminate user points –Such as USER: and CALC: user points.--. Do Something – A File * EXCEXTFUN. > Check return code. On error, quit...-CASE. ¦-PGM.*Return code is *Normal. ¦. ¦-*OTHERWISE. ¦ <-- *QUIT. '-ENDCASE '-- 3921 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
40
Return Code Processing: >Exit Processing Logic Add consistent standard text to CASE statement Use this if error processing is being handled by the calling function Beware of using exit inside internal functions –Internal functions should return control to calling function for error processing.--. Do Something – A File * EXCEXTFUN. > Check return code. On error, exit...-CASE. ¦-PGM.*Return code is *Normal. ¦. ¦-*OTHERWISE. ¦ Exit program – return code PGM.*Return code. '-ENDCASE '-- 4021 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
41
Return Code Processing: >Send Message and Ignore Processing Logic Add consistent standard text to CASE statement Use this if the called function is not required to continue processing –but the user is to be informed of the error Use generic error message in otherwise condition.--. Do Something – A File * EXCEXTFUN. > Check return code. On error, send message and ignore...-CASE. ¦-*OTHERWISE. ¦ Send error message ‘ERR Return Code invalid’. ¦ I *Return code = PGM.*Return code. ¦ I *Function = CON.Do Something. ¦ I *Synon name 1 = CON.A File. ¦ PGM.*Return code = CND.*Normal. '-ENDCASE '-- 4121 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
42
Return Code Processing: >Send Message and Quit Processing Logic Add consistent standard text to CASE statement Use this if error processing is being handled at the point where quit branches to in the function –but the user is to be informed of the error Beware of using quit to terminate user points –Such as USER: and CALC: user points.--. Do Something – A File * EXCEXTFUN. > Check return code. On error, quit...-CASE. ¦-PGM.*Return code is *Normal. ¦. ¦-*OTHERWISE. ¦ Send error message ‘ERR Return Code invalid’. ¦ I *Return code = PGM.*Return code. ¦ I *Function = CON.Do Something. ¦ I *Synon name 1 = CON.A File. ¦ <-- *QUIT. '-ENDCASE '-- 4221 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
43
Return Code Processing: >Send Message and Exit Processing Logic Add consistent standard text to CASE statement Use this if error processing is being handled by the calling function –but the user is to be informed of the error Beware of using exit inside internal functions –Internal functions should return control to calling function for error processing.--. Do Something – A File * EXCEXTFUN. > Check return code. On error, send message and exit...-CASE. ¦-PGM.*Return code is *Normal. ¦. ¦-*OTHERWISE. ¦ Send error message ‘ERR Return Code invalid’. ¦ I *Return code = PGM.*Return code. ¦ I *Function = CON.Do Something. ¦ I *Synon name 1 = CON.A File. ¦ Exit program – return code PGM.*Return code. '-ENDCASE '-- 4321 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
44
Default Return Code Values: >RTVOBJ as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set at start of procedure ¦ ¦-PGM.*Return code is NF ¦ * Set prior to USER: Processing if Data record not found ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 4421 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
45
Default Return Code Values: >CRTOBJ as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set at start of procedure ¦ ¦-PGM.*Return code is EX ¦ * Set prior to USER: Processing if Data record already exists ¦ ¦-PGM.*Return code is *Data update error ¦ * Set prior to USER: Processing if Data update error ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 4521 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
46
Default Return Code Values: >CHGOBJ as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set at start of procedure ¦ ¦-PGM.*Return code is *Record no longer on file ¦ * Set prior to USER: Processing if Data record not found ¦ * If not *Normal after that user point, an error message is sent ¦ ¦-PGM.*Return code is *Data update error ¦ * Set when record is locked or change error ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 4621 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
47
Default Return Code Values: >DLTOBJ as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set at start of procedure ¦ ¦-PGM.*Return code is *Record no longer on file ¦ * Set when record already deleted ¦ ¦-PGM.*Return code is *Data update error ¦ * Set when record is locked or delete error. ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 4721 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
48
Default Return Code Values: >EXCEXTFUN and Device External Functions as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set on CALL parameter list as input value ¦ * Set at start of program ¦ * Set on normal exit of program ¦ ¦-PGM.*Return code is *Error on call to program ¦ * Set if error on CALL operation ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 4821 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
49
Default Return Code Values: >EXCINTFUN as user inserted action Return code is not set by default generated code –Not even set to normal as in other function types Advisable to set to normal at start of function > Check return code..-CASE ¦-NOT PGM.*Return code is *Normal ¦ * General catch all for conditions not expected '-ENDCASE 4921 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
50
Default Return Code Values: >EXCUSRPGM as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Error on call to program ¦ * Set if error on CALL operation ¦ ¦-NOT PGM.*Return code is *Normal ¦ * General catch all for conditions not expected '-ENDCASE 5021 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
51
Default Return Code Values: >EXCUSRSRC as user inserted action Return code is not set by default generated code –Not even set to normal as in other function types Advisable to set to normal at start of function > Check return code..-CASE ¦-NOT PGM.*Return code is *Normal ¦ * General catch all for conditions not expected '-ENDCASE 5121 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
52
Default Return Code Values: >RTVMSG as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set on CALL parameter list as input value ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 5221 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
53
Default Return Code Values: >EXCMSG as user inserted action > Check return code..-CASE ¦-PGM.*Return code is *Normal ¦ * Set on CALL parameter list as input value ¦ ¦-PGM.*Return code is *Error on call to program ¦ * Set if error on CALL operation ¦ ¦-*OTHERWISE ¦ * General catch all for conditions not expected '-ENDCASE 5321 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
54
Default Return Code Values: >SNDERRMSG, SNDCMPMSG, SNDINFMSG and SNDSTSMSG as user inserted action Return code is not set by default generated code –Not even set to normal as in other function types Return code processing not required 5421 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
55
Bibliography: >McConnell, Steve 1993. Code Complete. Redmond, Washington: Microsoft Press. >Arthur, Lowell Jay 1988. Software Evolution. New York: John Wiley. >Coad, Peter and Yourdon, Edward 1991. Object-Oriented Design. Englewood Cliffs, New Jersey: Prentice-Hall. >Straker, David 1992. C Style: Standards & Guidelines. Hemel Hempstead, Hertfordshire: Prentice-Hall. 5521 September 2007 11A - Building High Quality Functions Copyright © 2007 CA
56
3rd Annual Plex/2E Worldwide Users Conference Page based on Title Slide from Slide Layout palette. Design is cacorp 2006. Title text for Title or Divider pages should be either 40 pt for short titles/28 pt for subtitles or 32 pts for longer titles/24 pt for subtitles. DATE text box is not on master and can be deleted. The date should always be 20 pts. 11A Building High Quality Functions Darryl Millington HawkBridge Pty Ltd September 21, 2007
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.