mircscripting.info Forum Index mircscripting.info
#mIRCscripting Forum
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Principles of Programming Applied

 
Post new topic   Reply to topic    mircscripting.info Forum Index -> Tutorials
View previous topic :: View next topic  
Author Message
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Mon Jan 05, 2004 9:16 pm    Post subject: Principles of Programming Applied Reply with quote

Writing good code is not just about making it work. Granted, that is most important; it doesn't matter how beautiful your code is if it doesn't accompish the task that you set out to do. However, with careful planning and a solid background in the principles of good programming, your code will meet other very important requirements. Your code should be:
- extensible
- modular
- readable
- efficient
- maintainable
I will define and explain the importance of each of these requirements.
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Mon Jan 05, 2004 9:20 pm    Post subject: Reply with quote

Extensible

Your code should be extensible; i.e., it should be possible to extend it to possible future uses of it. This is very important because it prevents you from needing to do a complete overhaul of your code if you realize that what you have is too restrictive or does not meet your needs anymore. Here is a very simple example. Let's say we want to write an alias that creates a picture window that displays a list of channels we're in:
Code:
alias chanpicwin {
  window -k0pf +d @cpw -1 -1 150 300
  drawrect -rf @cpw $rgb(0,0,30) 1 0 0 150 300
  var %i = 1 , %tot = $chan(0)
  while (%i <= %tot) {
    drawtext -r @cpw $rgb(200,200,200) tahoma 9 5 $calc((11 * (%i - 1)) + (%i * 5)) $chan(%i)
    inc %i
  }
}

In this example, everything is "hard-coded"--it doesn't have any options. I hope you like Tahoma, and I hope you like grey on navy.
When we write this, we should know that, eventually, we're probably going to change the theme of our script, or maybe we decide to distribute this script, and those who use it will have different tastes. We need to think ahead to what we may need in the future. For this reason, we should code the alias this way:
Code:
alias chanpicwin {
  var %bgcolor = $rgb(0,0,30)
  var %fgcolor = $rgb(200,200,200)
  var %font = tahoma
  var %fontsize = 9
  var %width = 150
  var %height = 300
  window -k0pf +d @cpw -1 -1 %width %height
  drawrect -rf @cpw %bgcolor 1 0 0 %width %height
  var %i = 1 , %tot = $chan(0)
  while (%i <= %tot) {
    drawtext -r @cpw $fgcolor %font %fontsize 5 $calc(($height($chan(%i),%font,%fontsize) * (%i - 1)) + (%i * 5)) $chan(%i)
    inc %i
  }
}

Although this accomplishes the same task as the first example, it is much easier to extend the function of this alias in the future. Notice that we've also changed the value of '11' in the calc to a calculation of the height of the string we're going to draw. We can now easily change those hard-coded values to options with very little effort:
Code:
alias chanpicwin {
  var %bgcolor = $1
  var %fgcolor = $2
  var %font = $3
  var %fontsize = $4
  var %width = $5
  var %height = $6
  window -k0pf +d @cpw -1 -1 %width %height
  drawrect -rf @cpw %bgcolor 1 0 0 %width %height
  var %i = 1 , %tot = $chan(0)
  while (%i <= %tot) {
    drawtext -r @cpw $fgcolor %font %fontsize 5 $calc(($height($chan(%i),%font,%fontsize) * (%i - 1)) + (%i * 5)) $chan(%i)
    inc %i
  }
}

Now the syntax for this alias is:
Code:
/chanpicwin <bgcolor> <fgcolor> <font> <fontsize> <width> <height>

And we can easily write an alias to open our own channel list window that is tailored to our script:
Code:
alias mycpw {
  chanpicwin $rgb(0,0,30) $rgb(200,200,200) tahoma 9 150 300
}

Now if we change the theme of our script, we can simply change the value of the options in one place. We can also rename the above alias and write a new one so that we don't lose the old values.
Of course, this is a very simple example of implementing extensibility. As you get more experience with programming and scripting, opportunities to implement an extensible design will be easier to recognise. It is important to remember that making something extensible after it has already been written defeats the purpose. So always plan your projects carefully before you type the first line of code. You need to ask yourself some questions: Will I ever want to make this code more powerful? Will I ever distribute this code to others? Will my use of this code ever change? How can I make these future changes as simple as possible?
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Tue Jan 06, 2004 1:50 pm    Post subject: Reply with quote

Modular

Your code should be modular; i.e., parts of it should be generalized so that it can be used for completely different applications. This can save you a lot of time by allowing you to reuse code instead of writing new, but only slightly different, code. Here is a simple example. Let's say we want to generate a session key for a bot's login:
Code:
alias bot.key {
  return botkey $+ $rand(a,z) $+ $rand(a,z) $+ $rand(1,9) $+ $rand(A,Z)
}

Later, we decide to write a webboard for mIRC that users connect to via DCC chat. We realize that we will be using the same type of login system, so we will need unique keys for the webboard users, as well. We write this alias:
Code:
alias board.key {
  return boardkey $+ $rand(a,z) $+ $rand(a,z) $+ $rand(1,9) $+ $rand(A,Z)
}

We realize that large portions of these two aliases are identical, so we choose to slim them down so that we're not duplicating code:
Code:
alias bot.key {
  return botkey $+ $randkey
}
alias board.key {
  return boardkey $+ $randkey
}
alias randkey {
  return $rand(a,z) $+ $rand(a,z) $+ $rand(1,9) $+ $rand(A,Z)
}

Now, if we ever decide that the random portion of these keys is not random enough, we can simply change the randkey alias to have a longer string of random characters. This creates a single point of maintenance, rather than two, like we had before. This is still a lot of code, though. This is even better:
Code:
alias key {
  return $1 $+ key $+ $rand(a,z) $+ $rand(a,z) $+ $rand(1,9) $+ $rand(A,Z)
}

Now we've greatly simplified this code. We've shortened it while leaving only a single point of maintenance. Now we just call $key(bot) or $key(board), depending on the type of key needed. Of course, this is a very simple example. The challenge is to notice more complex places in your code where such generalization can be implemented.

A large part of making code modular is breaking up tasks into functions or aliases. For instance, in the example above, we could have chosen from the start to generate the session key from within a larger block of code, say the actual on TEXT and on CHAT events that were being used for the login process. This would make generalization almost impossible without an overhaul of the code. Instead, we recognized the opportunity to generalize for modularity, and therefore put this part of the code in its own alias. A good rule of thumb is this: never meld together two tasks or two levels of processing into one block of code. Generating a key is different from authenticating a user; granted, these processes are dependent on each other, but they are not the same task. They should therefore be divided. Of course, the granularity of task division is really up to the programmer. This shows that planning is important to accomplishing modularity in your code. Here are some questions to ask yourself: Will I be doing something similar to this in another project? Would this code be useful to someone else for their own application? Will I be duplicating this code somewhere? Have I sufficiently broken up the levels of processes into their own blocks of code?
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Tue Jan 06, 2004 3:52 pm    Post subject: Reply with quote

Readable

Your code should be readable; i.e., it should be easy for you and others to understand your code. This is important because you may have to return to your code after not working on it for some time. During that time, you may have forgotten what a lot of the code does. Also, you may decide to distribute your code to others, or let others finish a project that you've lost interest in. In that case, those receiving your code will need to be able to read and understand what you've coded. There are several things that you can do to this end.

First, you must comment your code. I cannot stress how important it is to comment code. Some people say that comments are for weenies. I say that not commenting is for short-sighted idiots. Every complicated block of code should be commented. What does it do? How does it do it? What arguments does it expect? Especially complex lines should be commented, too. In mIRC script, there are a couple ways to comment code. I'm going to suggest some standards for commenting:
Code:
alias myalias {
  ; /myalias <arg1> <arg2> <arg3>
  ; returns a comdiddliation of the bombastication

  command arg arg arg
  command arg arg
  var %var = blah

  ; this bombasticates
  command arg
  command $id(arg,arg) $id($id($id($id(arg,arg),arg),arg),arg) | ; this command gets ready for comdiddliation by making sure the bombastication is correct

  ; this comdiddliates
  var %val = $id(arg,arg,arg)

  return %val | ; comdiddliation of the bombastication
}

Notice that we've placed comments in several places: 1) at the beginning of the alias to indicate the arguments that the alias expects and what the alias returns or does, 2) at the beginning of important blocks of code to indicate their purpose, and 3) at the end of important lines to explain what they do and how they do it.

Comments are not the only way to make code readable. The ONLY case where using a pipe ("|") is okay is for appending a comment to the end of a line. Putting multiple commands on the same line is messy, unreadable, and confusing. Anyone who helps in a scripting channel knows what it's like to try to debug someone's code that uses pipes. There is absolutely no reason to condense code in this way. It does not take any more bytes to put each command on a separate line, but it certainly improves readability.

Using variables, along with descriptive names, greatly improves readability, as well. For instance, say we write some code to remove every instance of "fyle" from mirc.ini to remove the Magnificent Fyle worm. We could write the code this way:
Code:
; clear all items in mirc.ini that contain "fyle"
var %topic = 1
while (%topic <= $ini(%path $+ mirc.ini,0)) {
  var %item = 1
  while (%item <= $ini(%path $+ mirc.ini,$ini(%path $+ mirc.ini,%topic),0)) {
    if (*fyle* iswm $readini(%path $+ mirc.ini,$ini(%path $+ mirc.ini,%topic),$ini(%path $+ mirc.ini,$ini(%path $+ mirc.ini,%topic),%item))) {
      remini %path $+ mirc.ini $ini(%path $+ mirc.ini,%topic) $ini(%path $+ mirc.ini,$ini(%path $+ mirc.ini,%topic),%item)
    }
    else { inc %item }
  }
  inc %topic
}

Not only is this code incredibly inefficient (as we'll discuss later), it is also unreadable. Imagine if someone pasted this in a channel and said, "Why doesn't this work?" Let's try something a little different:
Code:
; clear all items in mirc.ini that contain "fyle"
var %topic = 1 , %tottopics = $ini(%path $+ mirc.ini,0)
while (%topic <= %tottopics) {
  var %currenttopic = $ini(%path $+ mirc.ini,%topic)
  var %item = 1 , %totitems = $ini(%path $+ mirc.ini,%currenttopic,0)
  while (%item <= %totitems) {
    var %currentitem = $ini(%path $+ mirc.ini,%currentopic,%item)
    var %value = $readini(%path $+ mirc.ini,%currentopic,%currentitem)
    if (*fyle* iswm %value) {
      remini %path $+ mirc.ini %currenttopic %currentitem
      dec %totitems
    }
    else { inc %item }
  }
  inc %topic
}

This code is actually readable. Instead of humongous strings of identifiers, we have much shorter strings of variables. The variable names are descriptive, which helps us understand what each line does. Even if we do not understand how one of the variables gets its value (assuming that we're looking at someone else's code, here), we can still understand what the code does. The same cannot be said for the example above it. Making code readable also greatly increase productivity while coding. If your code is too complex, you will spend a lot of time going back to previously written code to remind yourself what it does and how it works. Readable, well-commented code takes little or no time to remember how it works.

Here are some questions to ask yourself while writing code: Will I understand this if I come back to it in three months? Would someone else be able to understand what this does? Do I have to use my horizontal scrollbar to read almost every line of code? Do I spend more time reminding myself what bits of code do than I spend on actually coding?
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Tue Jan 06, 2004 10:08 pm    Post subject: Reply with quote

Efficient

Your code must run efficiently; i.e., it must use as few CPU cycles as possible, without majorly sacrificing any of the other important characteristics of good code. Although much depends on the interpreter, or, if using compiled language, the compiler, there are still some things that a programmer can do to increase efficiency.

First of all, use local variables! Use the /var command, as seen in the previous section, to store data that will be used again. When you know that you will be performing the same calculation more than once, save it to a variable and refer to that variable instead. You can cut the processing time down significantly this way. This also applies to loop bounds. Let's look at an example. From the code above:
Code:
; clear all items in mirc.ini that contain "fyle"
var %topic = 1 , %tottopics = $ini(%path $+ mirc.ini,0)
while (%topic <= %tottopics) {
  var %currenttopic = $ini(%path $+ mirc.ini,%topic)
  var %item = 1 , %totitems = $ini(%path $+ mirc.ini,%currenttopic,0)
  while (%item <= %totitems) {
    var %currentitem = $ini(%path $+ mirc.ini,%currentopic,%item)
    var %value = $readini(%path $+ mirc.ini,%currentopic,%currentitem)
    if (*fyle* iswm %value) {
      remini %path $+ mirc.ini %currenttopic %currentitem
      dec %totitems
    }
    else { inc %item }
  }
  inc %topic
}

Let's analyze some statistics here. Let's say that a particular mirc.ini has 10 topics, and each topic has 10 items. That means that the inner loop will be iterated 100 times. This repitition is unavoidable; indeed, that's the purpose of the loops. However, there are some values we use more than once, which is why they've been saved to variables. For instance, take the variable %currentitem. Using the variable, its value only has to be retreived once. If a variable were not used, it would have to be retrieved twice. So we cut those data accesses in half--50%. Consider the variable %tottopics. Since we set the value of this variable at the start, we only have to count the total topics in mirc.ini once. If we didn't use a variable in the while comparison, this value would have to be re-calculated every time the loop iterated--10 times. We've cut those accesses by 90%. For almost any variable in the above code, you can see the drastic improvement--at least 50%--that each makes.

It is also important to consider how you store your data. For data that is going to be accessed often, storing on the harddrive in a text file is incredibly inefficient. Constant accesses to a disk can severely slow down your code. Hash tables and ini files are much faster because they are stored in memory. For data that does not require the structure inherent to an ini file, a hash table is usually more efficient. If an ini file is used for data that doesn't need structure, then most likely loops will be needed to find the desired data. Using a loop means that you will have to make many unsuccessful accesses to the data before the correct data is found.

Efficiency, for the most part, comes down to common sense. Combine loops; don't split two tasks on the same data into different loops unless absolutely necessary. Use variables wherever it saves even just one data access. Here are some questions you can ask yourself while coding: Does it seem like I'm hitting the same data over and over? Am I storing data on disk that should be in memory?
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Tue Jan 06, 2004 10:24 pm    Post subject: Reply with quote

Maintainable

Your code should be maintainable; i.e., it should be easy to upgrade and debug. This final requirement of good code depends mostly on the other four, especially the first three.

To be upgradable, your code must be modular and extensible so that certain parts can be replaced or changed without a complete overhaul of the code. I've used the term "single point of maintenance" several times in this tutorial, so now I'm going to explain it. When upgrading code, it is unproductive if you are forced to sift through every line to find places where code needs to be changed. It is better if the code can be changed in only one or a few places. This is a big reason why modularity and extensibility are so important. If we need to make a portion of the code configurable, it will be easy to add more options. If we need a more complex session key, we will only have to change a single alias.

The ease of maintaining code also depends on how readable it is. Upgrading code that you don't even understand is impossible. It is unproductive to be forced to reread and rethink the entire code before an upgrade can be performed. If the code is layed out simply and the comments are numerous and informative, then finding the exact block of code that needs to be upgraded is very easy.

Remember what it's like when you read about a major change to the script language in versions.txt. If your code is not maintainable, you will spend many hours upgrading your code for compatibility with the new client. Making code maintainable is not only for those to whom you distribute, but it is for you, also.


Conclusion

I hope you've found this tutorial informative. If you have any questions, comments, suggestions, or complaints, please feel free to post a reply or email me at moneo@rice.edu.

Feel the source, Luke.
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
holospoof
Major


Joined: 14 Jan 2004
Posts: 103

PostPosted: Sun Jan 18, 2004 4:14 am    Post subject: Reply with quote

very nice and detailed tutorial!!

nice job
_________________
blah!^!@^$^^
Back to top
View user's profile Send private message Visit poster's website
Mindless
Major


Joined: 27 Dec 2003
Posts: 114
Location: Antwerp, Belgium

PostPosted: Sun Jan 18, 2004 6:35 am    Post subject: Reply with quote

great job, well done
_________________
"An eye for an eye makes the whole world blind."
-- Gandhi

*bump*
Back to top
View user's profile Send private message MSN Messenger
moneo
Major


Joined: 27 Dec 2003
Posts: 58
Location: Savannah, GA, USA

PostPosted: Sun Jan 18, 2004 4:49 pm    Post subject: Reply with quote

Thanks!

If you have any questions or comments, feel free to post or find me in the channel.

Also, if you have any tutorial suggestions, just let me know.
_________________

me@lucasoman.com | http://lucasoman.com
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address Yahoo Messenger MSN Messenger
_Jacob_



Joined: 22 Dec 2005
Posts: 1

PostPosted: Thu Dec 22, 2005 2:29 pm    Post subject: Reply with quote

it took you a long time to do this tutorial, didn't it?
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    mircscripting.info Forum Index -> Tutorials All times are GMT - 5 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group