In the first part, the attid is being populated with the id of parent objects linked with the new attachments list in Trigger.new. But I don't get why we are using Trigger.old to get the parentids of the attachments in the list. Besides it's used only in case the Trigger.new is null. Is is for the case when an attachment is deleted?
Also, the way ids are extracted in case of trigger.new(attids.add(c.parentid);) and trigger.old(attids.add(Trigger.oldMap.get(c.id).parentid);) is pretty different. Couldn't we have just inserted the parentid directly like we did for trigger.new?
In line 7, I see that variable l of type testObject__c is not being used. Is it unnecessary?
I do understand the remaining part of the code. First a map(called parent) mapping the parent object ids with the list of total attachments linked to respective parent objects. The map only has those parentid that are linked with the attachments in attid. Then depending on the value mapped in parent, the count_attachment__c field is either assigned the size of the list or assigned 0. I assume the case of assigning 0 to the count__attachment__c occurs in case of delete action.
Wow, that code you found is...not good (putting it mildly). For someone who claims to just be starting out with Apex, you did a fine job analyzing that code.
For your first question, Pranay nailed it on the head. We need to use trigger.old because trigger.new is null in delete trigger contexts.
Your suggestions for your second and third questions are also correct.
I'd now like to spend some time showing you a much better implementation. You can probably use Andrew Fawcett's Declarative Lookup Rollup Summaries to do this without the need to write code yourself. The code I'll end up writing will likely bear some resemblance to the code generated by the DLRS tool.
For most people's use cases, the DLRS tool is sufficient. I, however, like to know/understand what's going on. So, with that, let's get to the code.
//   taking part in this invocation of the trigger.
// I prefer using a Set over a List when gathering data that can potentially be null.
//   to check for null inside the loop.
//   everything into one list, and write just a single loop.
// For Attachments, a parentId is required.
// Second step, count the number of attachments for each parentId.
// This quick ternary is a safeguard against null values.
//   for a parent means that parentId won't appear in the query results).
// To cause those parents to have their count updated to zero, we need another loop.
// Third step, updating the relevant records.
//   only ever pointing to a single SObject type).
// Thus, we need to exercise more caution than usual.
//   that tells us which field to update for each type of object.
//  a static type reference that the compiler can check for us.
// It also allows us to generate an SObject instance.
// We can perform DML on a List<SObject> that contains more than one type of object.
// However, we can only do this if there are 10 or fewer different SObject types in the list.
// Furthermore, the list can only switch between different SObject types a maximum of 10 times.
//   if you try to perform DML on it.
//   hand, would be perfectly fine.
45 lines of code (from the blog you found) compared to 41 lines of code (counting lines with just open/close braces, not counting completely blank lines or comments), and we had a considerable amount of extra work that needed to be done. If this were a more "normal" rollup, my code would have been a good deal shorter.
Like I mentioned, there are still improvements that could be made (like using a custom setting to define/store the mapping between SObjects you want to consider and the fields to roll up to), but this approach is miles ahead of the one you found.
Hope you can learn a lot from this.
1) Why Trigger.old is used?
A) Well, there is no need of it, you could directly use attids.add(c.parentid); as you get c from Trigger.old. Trigger.oldMap and Trigger.old have same values. One is in list format and other is in Map.
3) Why testObject__c l on Line 7 used?
The code is written here works, but its bit confusing and can be optimized further. Also the code is using for loop to count the size which is just overkill of CPU time.
If you use Aggregate Queries, the code will be faster as you are using database to tell you the count rather than counting urself in for loops.