Simple Ways To Be A Better Programmer (OSCON 2007)
"Simple Ways To Be A Better Programmer' as presented at OSCON 2007 by Michael G Schwern.
The audio is still out of sync, working on it. Downloading will be available once the sync is done.
2423 views | comments | 7 favorites | 0 downloads | 0 embeds (Stats)
More Info
This slideshow is Public
Total Views: 2423 on Slideshare: 2423 from embeds: 0
Slideshow Transcript
- Slide 1: Simple Ways
To Be A
Better Programmer
Michael G Schwern
schwern@pobox.com
- Slide 2: Discussion Session
D135 @ 5:30pm
- Slide 4: #!/usr/bin/perl -s
$;=$/;seek+DATA,!++$/,!$s;$_=<DATA>;$s&&print||$g&&do{$y=($x||=20)*($y||8);sub
i{sleep&f}sub'p{print$;x$=,join$;,$b=~/.{$x}/g}$j=$j;sub'f{pop}sub
n{substr($b,&f%$y,3)=~tr,O,O,}sub'g{$f=&f-1;($w,$w,substr($b,&f,1),O)[n($f-$x)+
n($x+$f)-(substr($b,&f,1)eq+O)+n$f]||$w}$w=\"\\40\";$b=join'',@ARGV?<>:$_,$w
x$y;$b=~s).)$&=~/\\w/?O:$w)ge;substr($b,$y)=q++;$g='$i=0;$i?$b:$c=$b;
substr+$c,$i,1,g$i;$g=~s?\\d+?($&+1)%$y?e;$i-$y+1?eval$g:do{$i=-1;$b=$c;p;i
1}';sub'e{eval$g;&e}e}||eval||die+No.$;
__DATA__
if($j){{$^W=$|;*_=sub{$=+s=#([A-z])(.*)#=#$+$1#=g}}
@s=(q[$_=sprintf+pop@s,@s],q[
if($j){{$^W=$|;*_=sub{$=+s=#([A-z])(.*)#=#$+$1#=g}} #_The_Perl_Journal_#
@s=(q[%s],q[%s])x2;%s;print\"\\n\"x&_,$_;i$j;eval}
])x2;$_=sprintf+pop@s,@s;print\"\\n\"x&_,$_;i$j;eval}$/=$y;$\"=\",\";print
q<#!/usr/local/bin/perl -sw
if(!$s){>.($_=<>).q<}else{@s=(q[printf+pop@s,@s],q[#!/usr/local/bin/perl -sw
if(!$s){>.(s$%$%%$g,tr=[=[===tr=]=]=||&d,$_).q<}else{@s=(q[%s],q[%s])x2;%s}
])x2;printf+pop@s,@s}
>
- Slide 7: Beyond syntax
- Slide 8: Tools for the
toolbox
- Slide 9: Easy To Do
- Slide 10: Hard To Screw Up
- Slide 11: Independent
- Slide 12: You can do them
alone
- Slide 15: Computer Science
+
People
=
Software Development
- Slide 19: Why learn?
- Slide 21: Cross-training is
sexay
- Slide 25: know
- Slide 26: Don’t know
- Slide 27: Don’t know you
don’t know
- Slide 28: “I don’t know”
- Slide 29: “Tell me about it”
- Slide 30: Why
- Slide 31: New things
mean failure
- Slide 37: New versions
- Slide 38: Hang out with
other folks who
learn
- Slide 39: Join the
community
- Slide 40: Broadcast your
problem
- Slide 41: Go be stupid
- Slide 42: Learn a really
different
language
- Slide 44: Look it up
yourself
- Slide 46: Wikipedia
- Slide 47: Relevant blogs
- Slide 48: Tech-specific
search sites
- Slide 51: > Thank you for your note. Opera is
> not a supported browser.
- Slide 52: Why not? I see absolutely no reason to
exclude Opera from the list of
supported browsers.
When I tell Opera to ID as Firefox, it
works as expected. No patches needed.
So please get back in sync with the
real world and also enable Opera.
- Slide 53: Topic for #eve is: Corp meeting on Vent: Saturday 14th and 21st
at 16:00 EVE time. All hands on deck! :-)
- Slide 54: <wibble> Announcing \"all hands on deck\" without announcing *why* is not
useful.
<wibble> I get enough shit from work, I certainly don't need any more
from a corp on Eve.
<wibble> just irritated at people telling me what I *have* to do.
- Slide 55: On Wed, May 09, 2007 at 08:34:52PM -0700, Michael G Schwern wrote:
> I'm sorry, but that's the automated failure report from CPAN::Reporter.
> I was doing an automated install.
- Slide 56: What can I do to force automated junk mailers
to follow my policy of bug reports?
- Slide 57: Speak with the CPAN::Reporter folks about what you would like to see in the
failed install report. Perhaps they can add in the Makefile.PL and make
output to the automatic report. This would be helpful, especially for
complicated builds like yours. Perhaps you could change your test
files so they automatically output the diagnostics you want on failure rather
than rely on the user to sift through the INSTALL docs to find the special
instructions
- Slide 59: “You’re right”
- Slide 60: someone screwed up the plan in
one of Encode's tests.
- Slide 63: +1
0
-1
- Slide 65: Bitching
is not
doing
- Slide 66: Means
- Slide 67: Ends
- Slide 68: Be Available
- Slide 70: Design <-> Code
- Slide 71: Interface -> User
- Slide 75: Get it out of
your head
- Slide 76: Documentation
- Slide 77: Wiki
- Slide 78: Tickets
- Slide 81: How are you?
- Slide 82: Having fun?
- Slide 83: Feel ok?
- Slide 87: Fear-based Programming
- Slide 88: Fear of change
- Slide 89: Fear of bugs
- Slide 90: Fear of getting fired
- Slide 91: Small Chunks
- Slide 92: Testing
- Slide 93: Test first
- Slide 94: Test during
- Slide 95: Test automaticly
- Slide 96: Stay at 100%
- Slide 97: Effective
Version
Control
- Slide 98: Code
- Slide 99: Test
- Slide 100: Commit
- Slide 101: How much?
- Slide 102: One Thing
- Slide 103: One Source Of Breakage
- Slide 104: ----------------------------------------------------------------------
r32185: schwern | 2007-06-29 18:22:55 -0700
Need a blank line preceeding POD directives.
----------------------------------------------------------------------
=== local/Test-Simple/lib/Test/Builder.pm
==================================================================
--- local/Test-Simple/lib/Test/Builder.pm (revision 32184)
+++ local/Test-Simple/lib/Test/Builder.pm (revision 32185)
@@ -983,6 +983,7 @@
# I'm not ready to publish this. It doesn't deal with array return
# values from the code or context.
+
=begin private
=item B<_try>
- Slide 105: ----------------------------------------------------------------------
r27706: schwern | 2007-03-16 15:19:55 -0700
Add a test to ensure an object can lie about being an IO::Handle.
----------------------------------------------------------------------
=== local/Test-Simple/t/is_fh.t
==================================================================
--- local/Test-Simple/t/is_fh.t (revision 27705)
+++ local/Test-Simple/t/is_fh.t (revision 27706)
@@ -11,7 +11,7 @@
}
use strict;
-use Test::More tests => 10;
+use Test::More tests => 11;
use TieOut;
ok( !Test::Builder->is_fh(\"foo\"), 'string is not a filehandle' );
@@ -34,3 +34,15 @@
unless defined *OUT{IO};
ok( Test::Builder->is_fh(*OUT{IO}) );
}
+
+
+package Lying::isa;
+
+sub isa {
+ my $self = shift;
+ my $parent = shift;
+
+ return 1 if $parent eq 'IO::Handle';
+}
+
+::ok( Test::Builder->is_fh(bless {}, \"Lying::isa\"));
- Slide 106: ----------------------------------------------------------------------
r27705: schwern | 2007-03-16 15:10:39 -0700
Comment about the nature of the filehandle in is_fh() was backwards.
----------------------------------------------------------------------
=== local/Test-Simple/lib/Test/Builder.pm
==================================================================
--- local/Test-Simple/lib/Test/Builder.pm (revision 27704)
+++ local/Test-Simple/lib/Test/Builder.pm (revision 27705)
@@ -1025,8 +1025,8 @@
my $maybe_fh = shift;
return 0 unless defined $maybe_fh;
- return 1 if ref $maybe_fh eq 'GLOB'; # its a glob
- return 1 if ref \\$maybe_fh eq 'GLOB'; # its a glob ref
+ return 1 if ref $maybe_fh eq 'GLOB'; # its a glob ref
+ return 1 if ref \\$maybe_fh eq 'GLOB'; # its a glob
return eval { $maybe_fh->isa(\"IO::Handle\") } ||
# 5.5.4's tied() and can() doesn't like getting undef
- Slide 107: ----------------------------------------------------------------------
r26467: schwern | 2006-12-24 12:36:01 -0800
Typo
----------------------------------------------------------------------
=== local/Test-Simple/Changes
==================================================================
--- local/Test-Simple/Changes (revision 26466)
+++ local/Test-Simple/Changes (revision 26467)
@@ -1,5 +1,5 @@
0.66 Sun Dec 3 15:25:45 PST 2006
- - Restore 5.4.5 compatibility (unobe@cpan.org) [rt.cpanorg 20513]
+ - Restore 5.4.5 compatibility (unobe@cpan.org) [rt.cpan.org 20513]
0.65 Fri Nov 10 10:26:51 CST 2006
- Slide 108: ----------------------------------------------------------------------
r17879: schwern | 2006-09-04 01:15:12 -0700
* Made most of the error messages report in the caller's context.
[rt.cpan.org #20639]
----------------------------------------------------------------------
=== local/Test-Simple/lib/Test/Builder.pm
==================================================================
--- local/Test-Simple/lib/Test/Builder.pm (revision 17878)
+++ local/Test-Simple/lib/Test/Builder.pm (revision 17879)
@@ -35,7 +35,7 @@
$$data = ${$_[0]};
}
else {
- die \"Unknown type: \".$type;
+ die(\"Unknown type: \".$type);
}
$_[0] = &threads::shared::share($_[0]);
@@ -50,7 +50,7 @@
${$_[0]} = $$data;
}
else {
- die \"Unknown type: \".$type;
+ die(\"Unknown type: \".$type);
}
return $_[0];
@@ -247,8 +247,7 @@
return unless $cmd;
if( $self->{Have_Plan} ) {
- die sprintf \"You tried to plan twice! Second plan at %s line %d\\n\",
- ($self->caller)[1,2];
+ $self->croak(\"You tried to plan twice\");
}
...
- Slide 109: Task branches
- Slide 110: BDUF
- Slide 111: Refactoring
- Slide 112: JIT Design
- Slide 113: Rename
- Slide 114: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $arg = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$arg\">/;
}
if ( my $arg = delete $parms{format} ) {
if ($arg eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$arg\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 115: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $arg = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$arg\">/;
}
if ( my $arg = delete $parms{format} ) {
if ($arg eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$arg\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 116: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 117: Extract
- Slide 118: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 119: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 120: if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
- Slide 121: if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
- Slide 122: method _content_as_text ($content) {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
my $formatted_content = $tree->as_text();
$tree->delete;
return $formatted_content;
}
- Slide 123: if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
$content = $self->_content_as_text($content);
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
- Slide 124: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 125: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
$content = $self->_content_as_text($content);
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 126: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
$content = $self->_content_as_text($content);
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 127: method _format_content ($format, $content) {
if ($format eq 'text') {
return $self->_content_as_text($content);
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
- Slide 128: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
$content = $self->_content_as_text($content);
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 129: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 130: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 131: method _check_unhandled_parms (%parms) {
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
}
- Slide 132: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 133: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
$self->_check_unhandled_parms(%parms);
} # is HTML
return $content;
}
- Slide 134: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
$self->_check_unhandled_parms(%parms);
} # is HTML
return $content;
}
- Slide 135: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
$self->_check_unhandled_parms(%parms);
}
return $content;
}
- Slide 136: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
if ($format eq 'text') {
require HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse($content);
$tree->eof();
$tree->elementify(); # just for safety
$content = $tree->as_text();
$tree->delete;
}
else {
$self->die( qq{Unknown \"format\" parameter \"$format\"} );
}
}
for my $cmd ( sort keys %parms ) {
$self->die( qq{Unknown named argument \"$cmd\"} );
}
} # is HTML
return $content;
}
- Slide 137: method content () {
my $content = $self->{content};
if ( $self->is_html ) {
my %parms = @_;
if ( exists $parms{base_href} ) {
my $base_href = (delete $parms{base_href}) || $self->base;
$content =~ s/<head>/<head>\\n<base href=\"$base_href\">/;
}
if ( my $format = delete $parms{format} ) {
$content = $self->_format_content($format, $content);
}
$self->_check_unhandled_parms(%parms);
}
return $content;
}
- Slide 138: Revealing Reuse
- Slide 139: refactoring.com
- Slide 140: Simplest Thing
- Slide 141: Optimizing
- Slide 142: Rule #1
- Slide 143: Rule #1: Don’t!
- Slide 144: Complicates
- Slide 145: Wastes Time
- Slide 146: Rule #2
- Slide 147: Rule #2: Don’t do it yet!
- Slide 148: Does it work?
- Slide 149: Is it tested?
- Slide 150: Is it documented?
- Slide 151: How fast?
- Slide 152: Throw Hardware
- Slide 153: Rule #3
- Slide 154: Rule #3: Profile First!
- Slide 155: Rule #4
- Slide 156: Rule #4: Better Algorithm
- Slide 157: Rule #5
- Slide 159: Estimating
- Slide 161: Feedback
- Slide 162: Mini Milestones
- Slide 163: • Get account activated
• Make simple test purchase
• Handle invalid expiration date
• Handle invalid address
• Record transactions
• Associate item with purchase
• Simple user form
- Slide 164: Quatloos
- Slide 165: Get Out Of
Technical Debt
NOW!
- Slide 166: Form
- Slide 167: Functionality
- Slide 168: Separate
- Slide 169: People
- Slide 170: Small Chunky Pieces
- Slide 171: Thank You
- Slide 172: Gabrielle
- Slide 173: Candy
- Slide 174: Selena
- Slide 175: Keith
- Slide 176: Everyone I pestered
- Slide 177: Discussion Session
D135 @ 5:30pm